Serverless: Things I wish I had known before I started - Part 1 - AWS Cognito
Last week I shared an article, “Serverless: Learn how to get it in your team without making them AWS Cloud Architect Experts”, encouraging companies and teams to implement Serverless architecture. However, as a reasonably recent launched architecture, the tool may not have (if they do at all) an easy way to implement some necessary features and this could give a hard time to new users. As a result, I am going to relate a sequence of things I wish I had known before start using Serverless as I believe they could be useful to someone else.
Authentication: What cognito offers and what is really needed.
Cognito User Pool is an AWS resource used for Serverless architecture and its purpose is to provide a cloud-based service where through an API (or other services such as Amplify) users can be authenticated. It provides several levels/types of configurations and its implementation is very straightforward.
Whilst having the opportunity to manage access control by an unique-independent tool can be mind-blowing and the level of configuration provided can cover almost all of the user needs, there are still some issues, such as token invalidation, lack of backup and personalized authentication that will require some extra work to be done by AWS.
Consider a web-application user case where after the user logs in and remains inactive for a while (lets say 10 mins) they will be logged out. Looks pretty simple right? Not really. Even though the Auth module from amplify automatically runs the token validation and refreshes the token when necessary, the minimum expiration time to be configured for a client is 1 hour. Hence, if there is a need to implement shorter expiration period, this will have to be done manually through the client.
Someone could be thinking: “This is not much of a problem, as this is an easy solution to implement”. There is no disagreement about that, if it was not for the fact that the token is never invalidated before 1 hour. Yes, the user logs out, but the generated token can still be used. In other words: The user logs in the application and receives a token, then, considering they have been inactive for a while the app detects it and executes the logout API method. The user is logged out, however, any request made to the API (using the same Cognito as authorizer) using this token will be accepted up to 1 hour. Unfortunately, this is yet to be solved but, at least, there is an open issue.
Integration Proxy validation
Proxy integration is an option where an API path can be configured in a way where every request, to this path, will be redirected to another configured url API.
Using proxy can be quite handy considering that not all of the methods will need to be re-implemented, however the application still needs to authenticate the user before forwarding their request. The most convenient option should be Cognito, but in this case it brings more limitation than flexibility.
Implementing Cognito as a proxy authentication is not much of a challenge as there is a simple way of configuring it, but what if some extra business logic is required to validate the user’s request? Imagine an application integrated with a third party service which returns the flight details of a given passenger. Even though the user A is authenticated in the app, they should not be able to request data of user B, unless they have the “Admin” role. In other words, just because a user has been registered with a password it does not mean they will have access to all application’s services. Unfortunately, the way that an integration proxy if configured, there is no lambda function executed before forwarding the request. Given this scenario, how can the user be validated against their details or role to access data from the proxy?
There is no way of doing it with Cognito as there is no option to execute a lambda function after the authorization.
While few people would have the thought (which is very logical) of going through Cognito’s configuration trying to find a way of executing a code after the request token validation, Cognito handles events related to registration and login, but not token validation. As a matter of fact, API Gatway is the component which handles the token validation using Cognito and then redirects the request to a Lambda or Proxy.
What could be a potential solution? Instead of using the Cognito built-in authorizer, build a custom lambda authorizer and then use it for the proxy endpoints. The benefit of this approach is the flexibility to define the rules based on user’s details, role and the request path and method. However, there are some trade-offs:
- The Cognito auth token will need to be decrypted manually. Moreover, it is not only an extra code to be written, but it also feels bad rewriting a code knowing it already exists.
- The Cognito token can not be cashed. Whilst the authorizer allows a cache period to be configured (this way, to save time, the API Gateway will skip the token validation until the cache expires), because every endpoint may have a different business rule, this scenario is very unlikely to work with it.
Another approach to be considered, is to re-write every singe method of the third party and make a request through a lambda function. This way, before making a request, it will be possible to validate the user’s details and role.
The goal is to understand how many third-party APIs will the app use and how many of their methods will be included. The more awareness, the easier will be to decide what could work better for the project.
What about user data backup?
As an independent access-control tool, Cognito has its own entities for users and groups, eliminating any need for an external DB storage. However, they do not provide a way of backing up or exporting these data - issue reference.
It is completely understandable that one of the principles of cloud services is the “trust”, meaning that they guarantee the service will always be available and stable. But what if someone accidentally deletes the Cognito resource? What if there is a need to migrate users to another service? Having in mind the reasons why there is a DB backup, the potential problems caused by the lack of these option are countless.
Although a simple decision of ignoring this would suffice, with the thought of prefering to be prepared for a situation rather than preparing after the situation, it was considered more secure finding a way of exporting the data. The solution found was to create a scheduled lambda (same concept as in a cron job ) which would call the Cognito API, pull the users’ details, export them to file and then upload it in some storage service. Because a npm collaborator wrote this great cognito-backup module which will do all the job of extracting the data to a file, no much of code will need to be written. As a result, the final solution was a script with 2 steps: a backup and then an upload.
As a new framework, there are some options which could be important for the business, but they could still be missing from the provider. As a result, some analytical revision will be necessary in order to check whether there is a possibility of a different solution or if it is not possible at all (and accept it). This writing is to share some “bad” experiences and a potential solution to them.
What is more, it is good to reinforce that regardless all the points highlighted, AWS and Serverless framework have been doing a great job in updating their services according to users’ needs, hence, there is no doubt that eventually few of these will have been fixed by any time soon.