Mike Jacobi

Logo


mikerjacobi@gmail.com
Machine Learning Engineer, Charmed AI
Resume PDF * LinkedIn

View My GitHub Profile

Design App Login

I wrote a prototype for an interior design app that, among other things, lets people create an account and chat with a designer. This system has two categories of users - clients and designers. They both login the same way, but their accounts are tagged so that the app handles them differently. This post is about how that works.

The login system is built using an AWS tech called Cognito. There’s two pieces to login - 1) authentication: who are you?, and 2) authorization: what can you do?. Cognito handles these two ideas with User Pools for authentication and Identity Pools for authorization. I store the account data generated by Cognito in an Accounts MySQL table, where I also store other account information, like name and email.

When a client registers an account, the email address and password get sent to Cognito. This adds the account to the User Pool. This is how the system knows who you are. In the AWS Cognito User Pool settings interface, you can configure details about how accounts work. You can control what a password requires, what the primary field for your username is (email address, phone number, etc). You can control what account verification looks like - ie, send a configurable email with a code, or a clickable verification link, send a text message with a code, or maybe don’t require verification at all. And you can also search for users and see their account data.

After registration and verification, the account can login/authenticate. By default, all users, client or designer, are assigned the “Authorized Role”. The Authorized Role is an AWS IAM user, managed by the Identity Pool, and it defines what can you do - the authorization piece of login. IAM is a system that defines the types of AWS actions that the IAM user may take. In this case, it means users can issue API calls to Lambdas via API Gateway. Immediately after registration, and in addition to being assigned the Authorized role, the React frontend issues an API call to the Create User Lambda to put the new Cognito User ID in the Accounts table. Ultimately, this is how the server can subsequently identify who is issuing API calls.

Every time a user logs in, the React frontend sends their username and password to Cognito. If successful, Cognito returns a little bit of data, called a token, back to the user’s browser. The token contains information like email address, Cognito User ID, how long the token is valid for, and group data (more coming here). The token is stored in the browser’s local cache, and whenever a user issues an API call, the token itself gets sent to the Lambda servers as a header, along with the rest of the data necessary for the API call. This is how the server knows who is calling it - because it reads the Cognito User ID out of the token that got sent in the API call, and looks up the account in the Accounts table.

There is also a notion of a Cognito User Pool “group”. After a user is added to the User Pool, AWS admins can lookup the user and assign them to a group. A group is just a tag that is associated with the authorization token that the user receives when they login. There can be any number of groups, and the name can be any string value. This is how designers are distinguished from clients. Both types of users are initially regular accounts. When a particular account should be a designer, an admin assigns that account to a group called “designer”. Then, whenever that account logs in, “designer” is added to the token.

There is logic in the React app that says, if ‘designer’ is in the token, then show the designer interface. If ‘designer’ isn’t in the token, assume it’s a regular client user, and show the client interface. Similarly, there is logic in the Lambdas that says, if ‘designer’ is in the token (again, because the token gets sent to the Lambdas as a header), then permit the account to do designer things, like modify the state of client accounts. This is fundamentally the mechanism that differentiates the two categories of users. It’s a good system because it lends itself to additional types of roles. For example, it would be straightforward to create a new group, “admin”, that can update the set of designer accounts or maybe block client accounts.

The last login concept that this project makes use of is “Single Sign On” (SSO). This project has two subdomains, each a disparate React app, named Design and Build. The first iteration at auth consisted of implementing a registration and login page on each app, both working exactly as described above. This isn’t a good pattern because it requires the registration and login pages be duplicated between Design and Build, and complicates making project wide changes. Cognito User Pools allow you to autogenerate an additional, dedicated login subdomain and app that is meant for this exact purpose.

This autogenerated Cognito login app provides a login page and facilitates OAuth2 code grant flows. This means that the Design and Build apps need to become OAuth2 clients. A client id can be generated for each of them in the Cognito Identity Pool configuration interface. When either the Design or Build app needs to register a new account or log a user in, they redirect their user to the login app using the client id that belongs to them. The user logs in, which generates an OAuth2 code and redirects their browser back to the Design or Build app accordingly. Then the code is exchanged for the aforementioned token, which contains Cognito User ID and group information. This is a nice dynamic because any number of additional apps can be created and leverage the same infrastructure.

Overall, Cognito is great to work with and makes implementing a top tier auth system significantly easier.