Using Cognito for users management in your Serverless application

One of the cool API Gateway features is the way it handles access control. Among other options, it can leverage IAM credentials or API keys to authorize your endpoints, but if none of that fits your needs it also has Lambda (formerly Custom) Authorizers, which pretty much lets you set your own rules. However, in certain cases you don’t only need a way of protecting your API, but you also want to let users register in your app, verify their email addresses, reset passwords or allow them to login and register using social providers such as Facebook. You could certainly build all those features from the ground up and store your users’ data in a database of your choice, but why would you do that when all you need to do is run serverless deploy? Cognito User Pools provides that and much more, just by adding some Cloud Formation resources to the serverless.yml file, your serverless app will have users management capabilities. (Working example here)

What is a Cognito User Pool?

Cognito User Pools is a managed user directory (don’t confuse this with Identity Pools). You can use it as an identity provider, so that anybody can register and sign in to your web/mobile app. It includes all the basic features you’d normally need regarding users’ management, such as email verification or password recovery, but we appreciate the value of Cognito when we notice that things like multi-factor authentication or social sign in can be added with a couple of clicks.

It’s important to note that the outcome of Cognito when a user logs in is a JSON Web Token, not AWS credentials, which means that you can pretty much use it with any API you build. However, if you’re using API Gateway, this task becomes much simpler, as Cognito already has a Lambda Authorizer you can use. Let’s see how we can integrate it in our Serverless app.

Creating a Cognito User Pool in your Serverless service

The first thing we need to create is the User Pool, which is basically the users’ directory. It allows you to configure certain security aspects, such as whether we enable multi-factor authentication or the requirements passwords should meet, the attributes you’d like to store about your users or if you prefer them to sign in using their username, email or phone number. In this case, we’ll let our users register with their email address and will require the password to have a minimum length of 6 characters, including an uppercase and a number. You just need to include the snippet below under the resources section of your serverless.yml file:

CognitoUserPool:
Type: "AWS::Cognito::UserPool"
Properties:
MfaConfiguration: OFF
UserPoolName: my-user-pool
UsernameAttributes:
- email
Policies:
PasswordPolicy:
MinimumLength: 6
RequireLowercase: False
RequireNumbers: True
RequireSymbols: False
RequireUppercase: True

The user pool is not enough on its own, we also need to create an App Client. The App Client allows applications (mobile, web, server-side, etc.) to issue requests to the Cognito APIs that are normally unauthenticated, such as APIs to register, sign in or recover passwords. We just need to add the resource below:

CognitoUserPoolClient:
Type: "AWS::Cognito::UserPoolClient"
Properties:
ClientName: my-user-pool-client
GenerateSecret: False
UserPoolId:
Ref: CognitoUserPool

That was pretty easy, but how can our users sign up? We could just go ahead and integrate the User Pool with our app using either the Javascript, Android or iOS SDKs. Even though that should be fairly straightforward, we’ll take an easier road: we’ll use Cognito’s Hosted UI.

Using the Hosted UI

Cognito also has a built-in front end that handles sign-up and sign-in, we only have to configure the URL of our app where users should be redirected after logging in or out. Unfortunately, this cannot be done through CloudFormation, so we need to go to the Cognito Dashboard in the AWS Console. Once there and after selecting our User Pool, we have to select App client settings on the sidebar, enter our callback URLs and check Implicit Grant along with all the OAuth scopes, which basically means that we’d like to get JSON Web Token back after the user has authenticated.

Cognito User Pool App Client Settings

Almost there, only one step left! Before you move on, take note of the Client ID that appears on top of the page, because you’re going to need it afterwards. Now go ahead and select Domain name, where you’ll create the domain your users will sign in and register from:

Cognito Domain Name Settings

And that’s it! Direct your users to https://<your_domain>/login?response_type=token&client_id=<your_app_client_id>&redirect_uri=<your_callback_url> and you won’t need to handle sign ins, registrations or password resets. Go ahead and replace the missing pieces in the previous URL with your own Cognito URL, Client ID and Callback URL, paste it into your browser and try it out yourself. After successfully signing in or registering, you’ll be redirected to https://<your_callback_url>/#id_token=123456789tokens123456789&expires_in=3600&token_type=Bearer, just remember that you’ll have to store the value of id_token in your front-end app to call the API.

Protecting API Gateway Endpoints

Most of the job is done at this point, but we still need to tell our API to accept incoming requests only if the user has successfully signed in. For that matter, we’ll create a new resource that holds the API Gateway authorizer pointing to the User Pool:

ApiGatewayAuthorizer:
DependsOn:
- ApiGatewayRestApi
Type: AWS::ApiGateway::Authorizer
Properties:
Name: cognito-authorizer
IdentitySource: method.request.header.Authorization
RestApiId:
Ref: ApiGatewayRestApi
Type: COGNITO_USER_POOLS
ProviderARNs:
- Fn::GetAtt: [CognitoUserPool, Arn]

Last but not least, we have to attach the newly created authorizer to the endpoints we’d like to protect:

registeredOnly:
handler: handler.hiUsers
events:
- http:
path: /hiUsers
method: get
authorizer:
type: COGNITO_USER_POOLS
authorizerId:
Ref: ApiGatewayAuthorizer

Now, whenever we want to access the /hiUsers endpoint, we must provide a valid id_token in the HTTP Authorization header.

Conclusion

Having users management capabilities in your Serverless app and protecting your API is now easier than ever thanks to Cognito User Pools. Hopefully, this post will help you getting everything up and running but, if you find it difficult to set up, you can take a look at a working example in this repo.