Urban

Identities and Keys and Tokens O MY!

aws codestar serverless codepipeline codebuild cloudformation lambda apigateway s3 iam cognito

One of the most important pieces of any app is authentication and authorization for end users. A post by Dan Bellinski details the complexity of digital security. If you’re new to the auth world like I am, you can get lost in just the terminology. In this post I’ll explain how you can utilize and integrate with AWS Cognito in your serverless app.

The difference between User Pools and Identity Pools

According to the documentation a user pool is

A user pool is a user directory in Amazon Cognito. With a user pool, your users can sign in to your web or mobile app through Amazon Cognito. Your users can also sign in through social identity providers like Facebook or Amazon, and through SAML identity providers. Whether your users sign in directly or through a third party, all members of the user pool have a directory profile that you can access through an SDK.

and an identity pool is

Amazon Cognito identity pools (federated identities) enable you to create unique identities for your users and federate them with identity providers. With an identity pool, you can obtain temporary, limited-privilege AWS credentials to access other AWS services.

There are a few similar terms and concepts between the two services. The distinguishing feature of a user pool is the fact that it is a user directory and can act as an authentication for an end client. Identity pools provide a unique id that can be used for use within AWS.

Federation

In general, the term federate means integrating or uniting separate entities. In our case, it’s integrating authentication providers outside AWS.

Cognito user pools integrate the various authentication platforms by providing a single auth mechanism for your client. Your client provides credentials to a user pool, and it does the rest. This way your client doesn’t have to worry about implementing multiple providers such as Facebook, Twitter, Google, etc.

Identity pools do a similar function, but for your AWS resources. After a platform authenticates a user, it can retrieve a unique id for that user. If a second platform authenticates the user, it will receive the same id. That id can then be used to access AWS resources. Your AWS resources then don’t need to concern themselves with how the user was authenticated.

Server Side

Hopefully what I’ve gone over so far helps differentiate user pools and identity pools. The AWS docs do a great job of explaining how to authenticate users, but to me, it wasn’t clear how to use and trust a request server side. Assuming a serverless architecture, there are two ways to trust a request.

Using User Pools

When a user is authenticated, the client receives 3 JSON Web Tokens. The IDToken, AccessToken, and the RefreshToken.

In short the IDToken contains OIDC Standard Claims about the user. The AccessToken allows you to access the user pool and make changes to the user. The RefreshToken allows you to refresh the AccessToken.

All 3 tokens can be passed to the server and utilized. You’ll most likely want to use the AccessToken and RefreshToken. The AccessToken contains the user sub which is the subject identifier for the user. You could use this value to uniquely identify the user server side and use the token to access their data.

To get the token server side, the client has to pass it in, most likely as a header. API will then have to map it to a request body for Lambda to consume. Finally, the Lambda function needs to validate the token. Luckily, there is a great example for us. Once you have a validate token you can execute your business logic and make cognito calls.

With The Identity Pool

Quite a bit of overhead there. Luckily, if your API Gateway is set up to use IAM authorization, there’s a much easier way.

Auth Flow

The previous section demonstrates the login step. Cognito is our Login Provider. The 3rd column represents an identity pool. AWS STS is out of scope for this post but feel free to research it!

The GetId call retrieves the unique identifier discussed earlier. And remember, that id is the same no matter the login provider. That id is then used to get credentials for temporary access to AWS resources. In our serverless architecture, the resource they need access to is the API Gateway.

If you’re using AWS SDKs, this is all done for you automatically. The id is available in mapping templates via $context.identity.cognitoIdentityId. As long as your lambda function is only invocable via API Gateway, you don’t need to validate the id. You’re good to go on business logic!

In some cases you may need to pass in the AccessToken. It depends on where you store your user data. If you want to user your user data, then you need the AccessToken to update the users data.

Bonus Script

Recently, Dan Bellinski were working on an app utilizing Cognito. He was working on the client and I was doing the server side work. Since we were working in parallel,I needed a way to log users in and hit endpoints via Postman. Here is the script I used to do that.

import boto3
import sys

identityclient = boto3.client('cognito-identity')
idpclient = boto3.client('cognito-idp')

#1
response = idpclient.admin_initiate_auth(
    UserPoolId='us-east-XXXXXXX',
    AuthFlow='ADMIN_NO_SRP_AUTH',
    AuthParameters={
        'USERNAME':sys.argv[1],
        'PASSWORD':sys.argv[2]
    },
    ClientId='XXXXXXXXXXXXXXX'
)

#2
idtoken = response['AuthenticationResult']['IdToken']

#3
response = identityclient.get_id(
    IdentityPoolId='us-east-1:XXXXXXXXXXXXXX',
    Logins={
        'cognito-idp.us-east-1.amazonaws.com/us-east-XXXXXXXX': idtoken
    }
)

#4
identityid = response['IdentityId']

#5
response = identityclient.get_credentials_for_identity(
    IdentityId=identityid,
    Logins={
        'cognito-idp.us-east-1.amazonaws.com/us-east-XXXXXXXXX': idtoken
    }
)

#6
print("Access Key: " + response['Credentials']['AccessKeyId'])
print("Secret Key: " + response['Credentials']['SecretKey'])
print("Session Token: " + response['Credentials']['SessionToken'])
  1. Authenticate the user against your user pool
  2. Get the IdToken out of the auth response
  3. Get the unique id of the user from the identity pool
  4. Get the unique id out of the identity pool response
  5. Get the temporary access tokens for AWS resources
  6. Since these are IAM credentials, they are similar to those you use for your own users

You can take these tokens and pop them into a Postman instance generated from your API Gateway.

And finally, here is a great session from re:Invent!

Author

Next post

Setting up the AWS CLI with pyenv

I wrote an earlier post on setting up the AWS CLI on macOS. After almost a year of learning AWS and Python, I discovered there is a much better way. The main driver here is avoiding impact to the Apple Python installation.

Read More →