Recently, Dan Baker and I completed a project which involved refactoring our monolithitcal application to pull out the authentication component, and make it a microservice of its own. As our company grows, and we hire more and more developers, a lot of the new development goes into building new microservices, and each of these microservices needs to authenticate all of their HTTPS requests. Instead of relying on our legacy authentication system, which is tied to CakePHP, an MVC framework, we decided to build a new serverless authentication microservice.
Our current system involved a CakePHP application, which was the legacy application that serves HTML pages, along with having JSON REST endpoints. Along with this, we also had a newer, cleaner Python based Flask application that only served as JSON REST endpoints for certain parts of the web application. Each of these applications were hosted on their on fleet of EC2 machines, protected by a load balancer.
The Authentication system we relied on was the in-built authentication system of CakePHP . The framework stored user sessions in the database, each session having an expiry time. To authenticate each request for our flask application, the frontend request will pick a JWT (JSON web token) variable stored in every html page that is served by CakePHP, and use that JWT token to authenticate itself against the Python application. The JWT token is created on the backend of the CakePHP application by a special key, which is used by the flask application to verify and decode the token. This special key was stored in the AWS parameter store as a config value. In order to allow the building of other micrservices in a seamless fashion, we decided to have authentication as a microservice, so that each application can directly authenticate against this Auth microservice.
The system we chose to build involved only AWS managed services; Cognito for the core authentication, Lambda for a serverless backend that can communicate with Cognito, and API Gateway as the interface. This way we don't need to provision any machines, given our nimble devops team.
In order to do the refactoring of the code, we were making major changes inside the library code of CakePHP, we had to be extremely careful of what we were doing. So, we decided to use our practice of 'feature flags'. Feature flags are essentially boolean configuration variables that enforces certain logic on the frontend or backend if a feature is 'on'. This allows us to quickly rollback a feature in case we experience severe unknown bugs in production.
The major development involved writing and testing the lambda functions . The functions we wrote were - login_user, register_user, verify_token, refresh_token and other functions like reset_password. These functions had the logic that communicated with Cognito. Cognito stored our users, their emails, and their passwords. We also had to configure an API Gateway that served as an interface for these lambda functions. Once all this development was done and tested, all we had to do was refactor parts of the cakephp and flask application to communicate with this API gateway for registering/logging in users, and verifying jwt tokens.
The login and register function verified that the email and password (in case of login) was valid, and returned an authentication object which contained an ID token and a Refresh token . The ID token was to be used for every verification request, and when decoded, it would spit out the user_id and email of the user. The Refresh token was to be used to refresh the ID token, which would be every 30 days or so. The ID token had an expiration time of 3 hours, and would effectively be refresh with user activity.
So, upon successfully logging in, a user's ID token and Refresh token is now stored in the session table, instead of the entire user object, which was the case with CakePHP. The ID token is also passed on to the flask application, which will now directly verify against the new authentication microservice, instead of verifying with the CakePHP application.
Of course, another big part of this development was to automate the creation of these AWS resources. With a little bit of help from our devops team, we wrote some Terraform scripts that can create these resources for different environments such as sandbox, prerelease and production. Terraform is a great technology to automate creation/deletion of cloud resources.
Now, this will enable other services to be built which can authenticate their requests directly with our Cognito system :) This was a huge step towards decoupling our monolithical application and hopefully takes us a long way. Hooray!