Sorry, the language specified is not available for this page

    Application Authentication Using Amazon Cognito and An Introduction to Key APIs

    September 27, 2019

    All web applications and mobile applications need a way to register or sign-up users and require a secured and robust mechanism to sign-in (login) users to authenticate into an application. Amazon Cognito (Cognito) provides powerful features to enable user authentication for applications, plus a simple way of implementing the solution.

    Amazon Cognito’s powerful features include Amazon Cognito User Pools, which provide a secure and scalable directory to store users and access control for AWS resources. These user pools also enable easy integration with applications including built-in user interfaces (UI) and custom branding, multi-factor authentication (MFA), as well as social identity federations such as Google, Facebook and through SAML and OIDC identity providers.

    Cognito also includes Amazon Cognito identity pools through which users can obtain temporary AWS credentials to access AWS services, support anonymous guest users, as well as the identity providers such as Cognito user pools, social sign-ins, OIDC/SAML identity providers and developer authenticated entities.

    Typically, the user authentication requirements for an application can be largely classified in the following ways:

    • Customer users or external users can be managed in a user directory for user sign-up and sign-in to access a web or mobile application. This blog post focusses on the necessary details to successfully implement Amazon Cognito for user profile management and authenticating into the applications for these types of users.
    • Corporate users who are typically defined in an LDAP based system or active directory profile needing to access an application. To add application-specific user attributes, Cognito can be viewed as managing the external users of the system and user sign-up. However, sign-in verification will be handled by LDAP authentication, and the registration of users to use the application can be done in Cognito. The techniques shared to store and retrieve application-specific users and their attributes are discussed below.
    • Users should be able to perform federated user identity management by signing in through their social identity providers such as Facebook, Google, Amazon and custom OIDC/SAML

    Amazon Cognito provides user pools and identity pools. Cognito User Pools provide an easy and secure way of user sign-up and sign-in functionality to mobile and web apps with a fully managed service that scales to support hundreds of millions of users. For our objective of maintaining and authenticating users, we will create a “User Pool” which is a user directory that provides sign-up and sign-in options for your application users. The User Pool includes a managed user directory, supports standard tokens including OAuth2, supports social media, enterprise federations, and provides an easy-to-use user interface.

    Identity pools provide AWS credentials to grant users access to other AWS services including social media and enterprise federation.

    User Pool can be created by using Console, CLI command, APIs or by using Cloud Formation Templates (CFT). We recommend using CFT for creation and update of the User Pool and application client. User Pool needs to be created with a set of attributes that are typically standard attributes or custom attributes.

    You can define which attributes are required and which attribute values can be changed by setting “mutable” as true. In this example, we used email as the main identifier; hence it is required to identify those instances which are true and mutable, versus false, which means it cannot be modified for a user. 

    An application client is required to be defined within a User Pool, granting permission to call unauthenticated APIs that do not have an authenticated user, such as APIs to register, sign in, and handle forgotten passwords.

    Use the Console to ensure the user attributes in this application client, which can read and/or write (required attributes are always writable), are selected. Select Enable username-password (non-SRP) flow for app-based authentication, as this is the standard and required for the Authentication. Additionally, custom authentication and secured server-based authentication are available as needed.

    Sections of the CFT YAML provided here for defining the user pool and app-client. 

    Resources:

    # Cognito

      UserPool:

        Type: "AWS::Cognito::UserPool"

        Properties:

          UserPoolName: !Sub "test-user-pool"

          AutoVerifiedAttributes:

            - email

          Schema:

            - Name: family_name

              AttributeDataType: String

              Mutable: true

              Required: true

            - Name: email

              AttributeDataType: String

              Mutable: false

              Required: true

            - Name: test_attribute1

              AttributeDataType: String

              Mutable: true

              Required: false

      UserPoolClient:

        Type: "AWS::Cognito::UserPoolClient"

        Properties:

          ClientName: !Sub "test-app-client"

          GenerateSecret: false

          UserPoolId: !Ref UserPool

          ExplicitAuthFlows:

            - USER_PASSWORD_AUTH

      #Cognito end

    Cognito provides the flexibility to define the password policies with a minimum number of characters for the password, and to enforce requirements such as numbers, special characters, uppercase letters, lowercase letters.

    Now, let’s review the typical workflow of onboarding and authenticating users to an application in the order of invite, sign-up, sign-in, viewing and updating user profile info and logging out process along with the APIs.

    CognitoIdentityProvider

    class CognitoIdentityProvider.Client

    A low-level client representing Amazon Cognito Identity Provider:

    import boto3

    cognitoclient = boto3.client('cognito-idp')

    Invite:

    Typically, a user gets invited to use an application by sending the link to register. It is better to maintain the list of invited users in a separate data table. It’s possible to create the invited user in Cognito by setting them up with the temporary password upon creation. However, if the user did not accept the invite to register, there could be redundant records. Also, it will be easy to manage it in a table to know the list of invited users along with their status.

    Sign-up or Registration:

    The User Registration form can accept the inputs like user ID (email), first name, last name, etc.

    This can be a two-step process of creating the user in Cognito, then sending the temporary password to the user’s email to reset it, or set up the user accepting the password in the registration form.

    Step-1: This method call will create the user in Cognito in a pending state:

    response = cognitoclient.admin_create_user(

    UserPoolId=self.user_pool_id,

    Username=email,

    UserAttributes=[

     {'Name': 'email','Value': email},

    {'Name': 'email_verified', 'Value': 'true' },

    {'Name': 'last_name', 'Value': last_name},

    {'Name': 'custom:test_attribute1', 'Value': test_value1}, ],

    TemporaryPassword=temp_password,

    ForceAliasCreation=True,

    MessageAction='SUPPRESS'

     )

    Step-2: These method calls will initiate the authentication flow and reset the password to move the user from pending state by responding to the authentication challenge, as in the condition:

    NEW_PASSWORD_REQUIRED.

    response = cognitoclient.initiate_auth( ClientId=client_id,

     AuthFlow='USER_PASSWORD_AUTH',

     AuthParameters={'USERNAME': email, 'PASSWORD': temp_password })

    The session which should be passed both ways in challenge-response calls to the service:

    Session_id=response['Session']

    Respond to the challenge with the new password to override user’s preferred password.

    response = cognitoclient.respond_to_auth_challenge(

     ClientId=client_id,

     ChallengeName='NEW_PASSWORD_REQUIRED',

     Session=session_id,

     ChallengeResponses={ 'NEW_PASSWORD':password ,  'USERNAME':email } )

    access_token= response['AuthenticationResult']['AccessToken']

    Through the Console, one can see the users in Active and Pending states, or using API method list_users.

    Alternatively, users can be added by using the “sign-up” method when there are no workflow requirements involved to add users.

    response = client.sign_up(

        ClientId='string',

        SecretHash='string',

        Username='string',

        Password='string',

        UserAttributes=[

            {

                'Name': 'string',

                'Value': 'string'

            },

        ],

        ValidationData=[

            {

                'Name': 'string',

                'Value': 'string'

            },

        ],

        AnalyticsMetadata={

            'AnalyticsEndpointId': 'string'

        },

        UserContextData={

            'EncodedData': 'string'

        }

    )

     Sign-in

    Sign-in or logging into an application involves these steps:

    response = self.cognitoclient.initiate_auth(

               ClientId=client_id,

               AuthFlow='USER_PASSWORD_AUTH',

               AuthParameters={'USERNAME': email, 'PASSWORD': password })

    If there is no issue with the authentication based on the parameters passed, then the access token will be made available to get the user details.

     access_token= response['AuthenticationResult']['AccessToken']i

    View user details

    User details can be accessed using the access token obtained during sign-in:

    getuserresponse =cognitoclient.get_user(

                       AccessToken=access_token )

    The user attributes will be available in the array with field names and values. Note the custom attributes will carry the “custom:” prefix in the response, as in the example below:

    User_attributes=getuserresponse['UserAttributes']

                    for i in range(len(User_attributes)):

                        fieldname= User_attributes [i]['Name']

                        if fieldname == "custom:test_attribute1":

                           test_attribute1= User_attributes [i]['Value']

                        if fieldname == 'last_name':

                           last_name=User_attributes[i]['Value']

    Update user attributes:

    The update_user_attributes method can be used by passing an array of attributes that needs to be updated along with their changed values, and by using the access_token obtained during sign-in.

    Sign-out:

    Access tokens obtained during sign-in can be invalidated using the sign-out functions. When a user clicks on a log out function in an application or needs to be signed out in other use cases, the following method should be used. The authentication token will be valid until the time expires in during sign-in, so it should be invalidated as and when needed.

    logoutresponse = cognitoclient.global_sign_out( AccessToken=access_token  )

    Administrator methods:

    Wherever there is a need for an administrator to execute functions such as to get user details, override the password, updating user attributes, signing out or deleting users, there are methods available that can be executed. The list below is only the partial list of admin functions:

    Password management:

    After initial creation of a user and meeting the challenge to reset the passwords, it should be possible for the user to change the password or reset a forgotten password.

    Change Password: User can do self-service by changing his password from the current know password.

    response = cognitoclient.change_password(

    PreviousPassword=password,

    ProposedPassword=newpassword,

     AccessToken=access_token )

    Forgot Password: User can reset the password using a confirmation code in the event of forgetting the password.

    response = cognitoclient.forgot_password(

                                ClientId=self.client_id,

                                Username=email)

    This method will send the confirmation code to a verified email or text to verified phone.

    response = self.cognitoclient.confirm_forgot_password(

                       ClientId=client_id, Username=email,

                       ConfirmationCode=confirmationcode,                         

                       Password=password)

    Conclusion: Amazon Cognito is a smart consideration for application authentication and identity management and is easy to get started with its powerful features, integration capabilities, user identities management and social and enterprise federation.

    References:

    https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/index.html

    https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp.html#CognitoIdentityProvider

    Other Posts You Might Be Interested In

    Porper (Portable Permission Controller) – RBAC Library for Microservices

    PORPER - Portable Permission Controller, an RBAC Library for Microservices Thanks to serverless frameworks like AWS Lambda, Google Cloud Function and Azure Functions,... Learn More

    Journey Through a Proof of Concept Application Development Using AWS Cloud Services

    I recently had the opportunity to develop a Proof of Concept (PoC) for an idea whereby data is available from PDFs and Excel files to be consumed and analyzed into a... Learn More

    CodeBuild, CodeDeploy and CodePipeline Quick Start Guide to go from Development to Production

    In this guide, I will give you a quick but complete example to create a development to production Continuous Deployment (CD) or Continuous Delivery (CD) pipeline. I want you... Learn More