Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Authentication and Authorization to Amazon Cognito With Lambdas

DZone 's Guide to

Authentication and Authorization to Amazon Cognito With Lambdas

Take a look at this tutorial on this difficult-to-find topic.

· Cloud Zone ·
Free Resource

Authentication

In our project, we were using Amazon Cognito for authentication, authorization and user management. It’s very easy to use, basically, you just need to create a user pool, identity pool, and users (everything you can “click” from AWS console).

I will not go into the details, you can read how to do this step by step from official AWS docs.

To authenticate from a web application you simply need to use this code:

    var authenticationData = {
        Username : 'username',
        Password : 'password',
    };
    var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
    var poolData = { UserPoolId : 'us-east-1_TcoKGbf7n',
        ClientId : '4pe2usejqcdmhi0a25jp4b5sh3'
    };
    var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
    var userData = {
        Username : 'username',
        Pool : userPool
    };
    var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
    cognitoUser.authenticateUser(authenticationDetails, {
        onSuccess: function (result) {
            var accessToken = result.getAccessToken().getJwtToken();

            /* Use the idToken for Logins Map when Federating User Pools with identity pools or when passing through an Authorization Header to an API Gateway Authorizer*/
            var idToken = result.idToken.jwtToken;
        },

        onFailure: function(err) {
            alert(err);
        },

    });


As you can see besides providing a username and password, we also need to create a user pool object, which requires pool ID and client ID.

Everything is straightforward; however, in our case, we had to authenticate to different user pools. Our use case also provides us a pool name but we don't know, between the pool ID and client ID, which one needs to be provided together with username and password. But how do we get them? One of the options would be to provide a hardcoded list of both IDs, but in such case, we would need to do a redeployment of the UI. There is also no JavaScript API method to get the user pool by its name.

However, AWS has Java Cognito SDK which supports all kinds of operations on user pools and users. So why not to try move authentication to lambda?

We created a simple lambda which get 3 parameters (username, password, pool name). Let’s take a look at Cognito API SDK. It is coming from:

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-cognitoidp</artifactId>
  <version>1.11.269</version>  
</dependency>


The interface which we are interested in is called AWSCognitoIdentityProvider.  To create default implementation, type:

AWSCognitoIdentityProvider cognito = AWSCognitoIdentityProviderClientBuilder.defaultClient();


To be able to invoke each API method you need to give your lambda the proper roles and permissions. Each API method is under a different action. You can define them in your SAM or configure it directly from AWS console. 

PolicyDocument:
  Statement:
    -
      Effect: "Allow"
      Action: [
        "cognito-idp:AdminInitiateAuth",
        "cognito-idp:DescribeUserPool",
        "cognito-idp:DescribeUserPoolClient",
        "cognito-idp:ListUserPoolClients",
        "cognito-idp:ListUserPools"
      ]


Now let’s look what this interface provides us. Unfortunately, we cannot get the user pool object by its unique name, only by its ID. But we can get all the user pools with their names so we can find ours and get all necessary data.

List<UserPoolDescriptionType> userPools =
    cognito.listUserPools(new ListUserPoolsRequest().withMaxResults(20)).getUserPools();


 UserPoolDescriptionType has a name, which we compare with our name and ID. With the ID, we can browse for the user pool object which will contain everything which we need for authentication.

ListUserPoolClientsResult response =
    cognito.listUserPoolClients(
        new ListUserPoolClientsRequest()
            .withUserPoolId(userPoolId)
            .withMaxResults(1)
    );

UserPoolClientType userPool =
    cognito.describeUserPoolClient(
        new DescribeUserPoolClientRequest()
            .withUserPoolId(userPoolId)
            .withClientId(
                response.getUserPoolClients().get(0).getClientId()
            )
    ).getUserPoolClient();


We can now authenticate the user. Since we are doing it on the server side, we can use a Non-SRP authentication flow and pass the username and password directly.

try {
    Map<String, String> authParams = new HashMap<>(2);
    authParams.put("USERNAME", loginRequest.getUsername());
    authParams.put("PASSWORD", loginRequest.getPassword());
    AdminInitiateAuthRequest authRequest =
        new AdminInitiateAuthRequest()
            .withClientId(userPool.getClientId())
            .withUserPoolId(userPool.getUserPoolId())
            .withAuthFlow(AuthFlowType.ADMIN_NO_SRP_AUTH)
            .withAuthParameters(authParams);
    AdminInitiateAuthResult result =
        client.adminInitiateAuth(authRequest);
    AuthenticationResultType auth = result.getAuthenticationResult();
} catch (final UserNotFoundException
    | NotAuthorizedException exception) {
    exception.printStackTrace();
}


 AuthenticationResultType contains accessToken , idToken   and refreshToken  so everything what our web client app needs.

Authorization

We also need to change authorization. Our existing implementation of authorization is based on the Cognito default mechanism. Example configuration:

securityDefinitions:
  CognitoUserPool:
    type: "apiKey"
    name: "Authorization"
    in: "header"
    x-amazon-apigateway-authtype: "cognito_user_pools"
    x-amazon-apigateway-authorizer:
      type: "cognito_user_pools"
      providerARNs:
      - !Ref UserPoolARN


Provider:

UserPoolARN:
  Type: String
  Default: 'arn:aws:cognito-idp:us-east-1:782624688943:userpool/us-east-xxx'

As you see here this security definition is connected to a concrete user pool which, in our case, will not work because of authenticating to multiple user pools. So here we need to write a lambda, but this time for authorization.

The authorization lambda is getting two parameters:

  •  authorizationToken which is our JWT accessToken  which is passed in header from our UI client
  •  methodArn it’s Amazon Resource Name of full method which is needed for returning auth policy

The first step is to verify the JWT against public keys which are separate for each user pool. They are here:

https://cognito-idp.us-east-1.amazonaws.com/%s/.well-known/jwks.json

Where "%s" is, the user pool ID should be,  which you can take from processing accessToken  (part of issuer). I will not write here details on how to verify the suck key I used jose4j. For this, you can check examples (using the https jwks endpoint).

After verifying jwt we need a return policy which will tell AWS to allow or deny the request. This is a little bit tricky because the authorization policy needs to have concrete fields, but this is very well-described by Jack Kohn in AWS Labs.

In our case, principalId  is a JWT subject.

Now we only need to configure our lambda in SAM and we are done:

securityDefinitions:
  AuthorizationLambda:
    type: "apiKey"
    name: "Authorization"
    in: "header"
    x-amazon-apigateway-authtype: "custom"
    x-amazon-apigateway-authorizer:
      authorizerCredentials:
        Fn::GetAtt: [ RestApiAuthorizerRole, Arn ]
      authorizerUri:
          Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AuthorizationFunction.Arn}/invocations
      authorizerResultTtlInSeconds: 0
      type: "token"

During the implementation of authentication and authorization via Lambdas, it wasn’t easy to find something about this topic. It’s because our use case was not typical and now when you write a Javascript client you will simply use the js cognito API to do this. I hope this short article/tutorial will be helpful.

Topics:
amazon aws ,java ,cloud ,security ,authentication ,user group

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}