The simplest server configuration for your AWS Amplify/Cognito App
This article is about the most basic configuration to make Amplify work with traditional web frameworks like Flask. It can be used as a reference for Django, ExpressJS, PHP etc.
The authentication, in a mobile or web app, is one of the most important aspects, both on the business and on the technical side. Once you have the authentication layer, in which you know exactly who is accessing to your server’s resources, you can add the authorization layer, that makes possible to decide if a specific user can access to a specific resource.
In the last couple of years, a few best practices came out and made this process safer, easier and faster. In the mobile and web SPA (single page application) context, REST is one of the standard ways to make the client communicate with the server, along with the more recent GraphQL.
REST is an architectural style to make systems communicate to each other (Client and Server in this case), and it is stateless, which means that the server does not keep a ‘state’ in its memory about the authenticated users. The JWT token-based authentication is one of the best ways to be compliant with the stateless constraint.
An established way to do something opens up the opportunity for 3rd party players to provide this established-way-as-a-service.
Indeed, AWS created its authentication-as-a-service called AWS Cognito, which provides the normal username-password based auth and the 3rd party authentication (Facebook, Google etc.) by also allowing the multi-factor-authentication (SMS check and other personalized challenges) like shown in the image below (from the AWS documentation)
AWS also provides AWS Amplify, which is a wider framework that covers some essential aspects like the internationalisation, authentication, analytics and other services. The authentication part is built on top of AWS Cognito.
The documentation here: https://aws-amplify.github.io
Amplify has the client SDK ready for web and mobile apps. There is a lot of great articles for the client side integration like the one from React Native Training (https://medium.com/react-native-training/building-serverless-mobile-applications-with-react-native-aws-740ecf719fce).
The client side integration is straightforward. The article above is about a serverless approach (without having your own traditional server). Amplify is great for a serverless architecture because it provides a ready-to-go authentication layer for the other AWS services like S3 (for objects/static files), Lambda (to execute functions without a context) and many other services.
That’s great, but what about a traditional web server like Flask, Django, ExpressJS, PHP?
Let’s start from the React Native client.
The following JS snippets show the sign in method and how to get the current session values that will be used in the server. In this case, we use Auth.signIn for development purposes because AWS Amplify’s default login screen signs it automatically, hence you don’t have to be worried about it.
current session values:
The currentSession object contains the following JSON structure:
You need to add jwtToken from idToken to every authenticated request by setting the “Authorization” request’s header to the token value. You also have to add the jwtToken from accessToken by adding a new accessToken header.
The server will get the token value from the header and will provide the user reference to your view (which in Flask words is the handler bound to the http method and the uri).
I will use Flask for this article, but the concept can be extended to all the other languages and frameworks.
The following python snippet is a very simple Flask app:
Then we add the authentication layer by creating a custom auth decorator that wraps the view (like for if you use flask-login or flask-jwt) and provides the user reference to the Flask request object:
Before we continue, we have to add a few imports:
The aws_amplify_login_required decorator is defined as follows:
The decorator is based on the get_identity function that gets or creates the user starting from the data retrieved from AWS by using the token passed through the Authorization header:
The get_identity function gets the user data from AWS by using the decode_token function defined below:
Values like the Cognito User Pool Id and Client ID have to be taken from your AWS Cognito dashboard or from AWS CLI.
And that’s it!
This is the most basic configuration. If you want to enable the verify_at_hash feature, you have to change the jwt.decode arguments by removing the options argument and adding the access_token argument in which you pass the access_token value that you already passed in the request headers.
One important consideration: the most simple Flask request takes 7ms in my laptop, which is nothing compared to the 300ms of every request that checks the token against the AWS server. It means that this approach is quick and suitable for a fast prototyping process or for a project that does not require a low latency. In case you want to cut off the request time, you can consider to use a slightly different approach.
Amplify’s client SDK refreshes its token automatically (the token expires after one hour), which means that your mobile/web client has always a working token. On top of it, you can add your own Flask-JWT auth system by using the AWS token as a starting point, then you set a short expiration time for your token and you define a refresh strategy for your token based on the AWS token. This way, you have a 300ms request every 30/60 minutes and all the other requests with way less time (due to the fact that the token is decrypted by your server directly). In this case, the decorator in each request will change and the aws_amplify_login_required decorator becomes a normal function used by your own refresh strategy.
Enjoy your fast prototyping auth system!