Makes working with AWS Cognito easier for Python developers.
WARNING: to use this addition, you need to install this version of warrant
through github (preferably). That means you'll have to somehow "instruct"
django-warrant
to use this library instread of the upstream warrant.
MFA functionality is implemented without breaking the current interface by the
use of excpetions as actions that the caller needs to take. The authenticate
method is also refactored to make the new design fit a little bit smoother in
the code. Here's how MFA might be used in django:
-
Create a cognito user pool, and assign a domian name. Create a client app in that user pool. If you don't want to force mfa for all users, then it has to be enabled on a per user basis:
- Call the "associate software token" api through aws cli or boto3.
- Pass the secret to the TOTP app, obtain an OTP from the app.
- Use the obtained OTP with the "validate software token" api.
- Use the "set user mfa preference" api to enable mfa for the user.
- define user attribute mappings (cognito to db) using django-warrant
-
Here's warrant's workflow with MFA:
```python cognito_user = Cognito(cognito_app_id, cognito_clinet_id, username=cognito_username_to_login, access_token=aws_access_token, access_key=aws_access_key, secret_key=aws_secret) try: cognito_user.authenticate(Password=password_of_user) except ProvideMFATokenAction as e: tokens = e.provide_mfa_code(client_otp_code) ```
The way this works is that when we encounter an mfa challenge in the middle of the awssrp flow, we notify this event all the way back tothe caller (which is prepared for this by a
try ... except
block. The caller then proceeds to use the "functor" that is supplied to it as an "exception" to call back "into" the authentication function by providing it the mfa code, and everything is picked up where it has been left off, with the new functor returning the tokens. However except for the case that we have the username, password, and the otp password all in the same django view handler (as opposed to having username/pass and then proceeding to ask for the otp), we need to makedjango-warrant
able to handle this addition too. -
Apart from warrant difficulties, here are a few points about django-warrant: The request object has to be passed in:
python authenticate(request=request, username=email, password=password)
-
In Django settings: ```python import boto3
COGNITO_USER_POOL_ID = user_pool_id COGNITO_APP_ID = cognito_app_id boto3.setup_default_session(region_name='us-east-1', , aws_access_key_id=aws_access_key, aws_session_token=aws_session_token) # if on ec2, we can just assume the role attached to the instance: session = boto3.Session() credentials = session.get_credentials() token = credentials.token key_id = credentials.access_key secret_key = credentials.secret_key boto3.setup_default_session(region_name='us-east-1', aws_secret_access_key=secret_key, aws_access_key_id=key_id, aws_session_token=token) AUTHENTICATION_BACKENDS = [ 'django_warrant.backend.CognitoBackend', 'django.contrib.auth.backends.ModelBackend', ] ```
- Python Versions Supported
- Install
- Environment Variables
- COGNITO_JWKS (optional)
- Cognito Utility Class
warrant.Cognito
- Cognito SRP Utility
warrant.aws_srp.AWSSRP
- Projects Using Warrant
- Authors
- Release Notes
- 2.7
- 3.6
pip install warrant
Optional: This environment variable is a dictionary that represent the well known JWKs assigned to your user pool by AWS Cognito. You can find the keys for your user pool by substituting in your AWS region and pool id for the following example.
https://cognito-idp.{aws-region}.amazonaws.com/{user-pool-id}/.well-known/jwks.json
Example Value (Not Real):
COGNITO_JWKS={"keys": [{"alg": "RS256","e": "AQAB","kid": "123456789ABCDEFGHIJKLMNOP","kty": "RSA","n": "123456789ABCDEFGHIJKLMNOP","use": "sig"},{"alg": "RS256","e": "AQAB","kid": "123456789ABCDEFGHIJKLMNOP","kty": "RSA","n": "123456789ABCDEFGHIJKLMNOP","use": "sig"}]}
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id',
client_secret='optional-client-secret'
username='optional-username',
id_token='optional-id-token',
refresh_token='optional-refresh-token',
access_token='optional-access-token',
access_key='optional-access-key',
secret_key='optional-secret-key')
- user_pool_id: Cognito User Pool ID
- client_id: Cognito User Pool Application client ID
- client_secret: App client secret (if app client is configured with client secret)
- username: User Pool username
- id_token: ID Token returned by authentication
- refresh_token: Refresh Token returned by authentication
- access_token: Access Token returned by authentication
- access_key: AWS IAM access key
- secret_key: AWS IAM secret key
Used when you only need information about the user pool (ex. list users in the user pool)
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id')
Used when the user has not logged in yet. Start with these arguments when you plan to authenticate with either SRP (authenticate) or admin_authenticate (admin_initiate_auth).
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id',
username='bob')
Used after the user has already authenticated and you need to build a new Cognito instance (ex. for use in a view).
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id',
id_token='your-id-token',
refresh_token='your-refresh-token',
access_token='your-access-token')
Register a user to the user pool
Important: The arguments for add_base_attributes
and add_custom_attributes
methods depend on your user pool's configuration, and make sure the client id (app id) used has write permissions for the attriubtes you are trying to create. Example, if you want to create a user with a given_name equal to Johnson make sure the client_id you're using has permissions to edit or create given_name for a user in the pool.
from warrant import Cognito
u = Cognito('your-user-pool-id', 'your-client-id')
u.add_base_attributes(email='[email protected]', some_random_attr='random value')
u.register('username', 'password')
Register with custom attributes.
Firstly, add custom attributes on 'General settings -> Attributes' page. Secondly, set permissions on 'Generals settings-> App clients-> Show details-> Set attribute read and write permissions' page.
from warrant import Cognito
u = Cognito('your-user-pool-id', 'your-client-id')
u.add_base_attributes(email='[email protected]', some_random_attr='random value')
u.add_custom_attributes(state='virginia', city='Centreville')
u.register('username', 'password')
- username: User Pool username
- password: User Pool password
- attr_map: Attribute map to Cognito's attributes
Authenticates a user
If this method call succeeds the instance will have the following attributes id_token, refresh_token, access_token, expires_in, expires_datetime, and token_type.
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id',
username='bob')
u.authenticate(password='bobs-password')
- password: - User's password
Authenticate the user using admin super privileges
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id',
username='bob')
u.admin_authenticate(password='bobs-password')
- password: User's password
Sends a verification code to the user to use to change their password.
u = Cognito('your-user-pool-id','your-client-id',
username='bob')
u.initiate_forgot_password()
No arguments
Allows a user to enter a code provided when they reset their password to update their password.
u = Cognito('your-user-pool-id','your-client-id',
username='bob')
u.confirm_forgot_password('your-confirmation-code','your-new-password')
- confirmation_code: The confirmation code sent by a user's request to retrieve a forgotten password
- password: New password
Changes the user's password
from warrant import Cognito
#If you don't use your tokens then you will need to
#use your username and password and call the authenticate method
u = Cognito('your-user-pool-id','your-client-id',
id_token='id-token',refresh_token='refresh-token',
access_token='access-token')
u.change_password('previous-password','proposed-password')
- previous_password: - User's previous password
- proposed_password: - The password that the user wants to change to.
Use the confirmation code that is sent via email or text to confirm the user's account
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id')
u.confirm_sign_up('users-conf-code',username='bob')
- confirmation_code: Confirmation code sent via text or email
- username: User's username
Update the user's profile
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id',
id_token='id-token',refresh_token='refresh-token',
access_token='access-token')
u.update_profile({'given_name':'Edward','family_name':'Smith',},attr_map=dict())
- attrs: Dictionary of attribute name, values
- attr_map: Dictionary map from Cognito attributes to attribute names we would like to show to our users
Send verification email or text for either the email or phone attributes.
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id',
id_token='id-token',refresh_token='refresh-token',
access_token='access-token')
u.send_verification(attribute='email')
- attribute: - The attribute (email or phone) that needs to be verified
Returns an instance of the specified user_class.
u = Cognito('your-user-pool-id','your-client-id',
id_token='id-token',refresh_token='refresh-token',
access_token='access-token')
u.get_user_obj(username='bjones',
attribute_list=[{'Name': 'string','Value': 'string'},],
metadata={},
attr_map={"given_name":"first_name","family_name":"last_name"}
)
- username: Username of the user
- attribute_list: List of tuples that represent the user's attributes as returned by the admin_get_user or get_user boto3 methods
- metadata: (optional) Metadata about the user
- attr_map: (optional) Dictionary that maps the Cognito attribute names to what we'd like to display to the users
Get all of the user's attributes. Gets the user's attributes using Boto3 and uses that info to create an instance of the user_class
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id',
username='bob')
user = u.get_user(attr_map={"given_name":"first_name","family_name":"last_name"})
- attr_map: Dictionary map from Cognito attributes to attribute names we would like to show to our users
Get a list of the user in the user pool.
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id')
user = u.get_users(attr_map={"given_name":"first_name","family_name":"last_name"})
- attr_map: Dictionary map from Cognito attributes to attribute names we would like to show to our users
Returns an instance of the specified group_class.
u = Cognito('your-user-pool-id', 'your-client-id')
group_data = {'GroupName': 'user_group', 'Description': 'description',
'Precedence': 1}
group_obj = u.get_group_obj(group_data)
- group_data: Dictionary with group's attributes.
Get all of the group's attributes. Returns an instance of the group_class. Requires developer credentials.
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id')
group = u.get_group(group_name='some_group_name')
- group_name: Name of a group
Get a list of groups in the user pool. Requires developer credentials.
from warrant import Cognito
u = Cognito('your-user-pool-id','your-client-id')
groups = u.get_groups()
Checks the exp attribute of the access_token and either refreshes the tokens by calling the renew_access_tokens method or does nothing. IMPORTANT: Access token is required
u = Cognito('your-user-pool-id','your-client-id',
id_token='id-token',refresh_token='refresh-token',
access_token='access-token')
u.check_token()
No arguments for check_token
Logs the user out of all clients and removes the expires_in, expires_datetime, id_token, refresh_token, access_token, and token_type attributes.
from warrant import Cognito
#If you don't use your tokens then you will need to
#use your username and password and call the authenticate method
u = Cognito('your-user-pool-id','your-client-id',
id_token='id-token',refresh_token='refresh-token',
access_token='access-token')
u.logout()
No arguments for check_token
The AWSSRP
class is used to perform SRP(Secure Remote Password protocol) authentication.
This is the preferred method of user authentication with AWS Cognito.
The process involves a series of authentication challenges and responses, which if successful,
results in a final response that contains ID, access and refresh tokens.
The AWSSRP
class takes a username, password, cognito user pool id, cognito app id, an optional
client secret (if app client is configured with client secret), an optional pool_region or boto3
client.
Afterwards, the authenticate_user
class method is used for SRP authentication.
import boto3
from warrant.aws_srp import AWSSRP
client = boto3.client('cognito-idp')
aws = AWSSRP(username='username', password='password', pool_id='user_pool_id',
client_id='client_id', client=client)
tokens = aws.authenticate_user()
Twitter: @brianjinwright GitHub: @bjinwright
GitHub: @ebpetway
GitHub: @armicron