Terraform AWS: User Authentication with Cognito, API Gateway, React

Create a serverless architecture for a React application that uses AWS Cognito, API Gateway, and Lambda for user registration and authentication. The application also takes a look at protected routes with React Router and getting retrieving user profile information from DynamoDB.

Wed Feb 28 2024 00:00:00 GMT+0000 (Coordinated Universal Time)

Table of Contents

Introduction

Cogneato is a React app plus Terraform modules to create a serverless architecture for authenticating users. AWS Cognito is loaded with features but light on documentation and examples so being able to quickly recreate and experiment with Terraform has been convenient. Cogneato is also a way to test different authentication and authorization with API Gateway and Cognito Authorizer or custom auth with Lambda.

Terraform makes the following resources in AWS: CloudFront, API Gateway, 2 Lambda Functions and an S3 Bucket to host the React app. Cognito User Pool(with an App Client), Cognito Identity Pool.

Starting Point

  • Terraform installed with access to the AWS account that has administrator access for creating resources
  • nodejs v18+(download): Cogneato uses React 18 with React Router 6. nvm is a popular node version manager if interested.
  • Project Files: git clone instructions are later in this article but Github link

Terraform - Create AWS Infrastructure

The Terraform modules and React app are in the same repository but different directories. This isn’t ideal, but fine for this purpose. The Terraform modules are in the infra directory.

AWS Credentials

Terraform needs AWS credentials and there are a variety of ways to provide that. If you haven’t done that already, read below and view the Terraform AWS provider documentation.

Do not hard code AWS Access Keys into the Terraform files since by default Terraform state is stored in plain text.

If necessary, in infra/providers.tf you can add shared_credentials_file or profile. You can see details in the Terraform AWS Provider docs.

  1. Clone the repository with the Terraform files git clone git@github.com:ryanef/terraform-react-cognito.git
  2. Change into the directory: cd terraform-react-cognito
  3. Change into the infra directory: cd infra
  4. terraform init
  5. terraform plan
  6. terraform apply

The CloudFront distribution can take a long time to create, so this may take 5-10 minutes.

COPY ENVIRONMENT VARIABLES FROM TERRAFORM OUTPUT

There’s a Bash script in the infra directory named envvar.sh that will copy the values of API Gateway URL, Cognito User Pool ID, App Client ID, CloudFront domain URL, S3 Bucket Name, etc and put them in the React app’s .env file. You can always run the terraform output command if you want to manually copy and paste them into react/.env and react/.env.production.

  1. Make the script executable sudo chmod +x envvar.sh
  2. Run the script: ./envvar.sh
  3. Verify that react/.env and react/.env.production have been updated

React Application Setup

From the root of the Cogneato project folder there is an infra folder and react folder. If you’ve followed the above environment variable instructions, most of the configuration for a default deployment should be done. We just have to setup React and test it locally.

  1. Change into react directory: cd react
  2. Install React and dependencies: npm install
  3. Run a local web server and test React: npm run dev
  4. Open the localhost link it shows

UnexpectedSignInInterruptionException

I ran into this problem in development but not production. The project is using cookies to store the JWT tokens, not localStorage. Registering a user worked, logging in/authenticating caused the error. My issue was a domain mismatch in the cookie configuration. In production, the domain name was always an exact match for the cookies so the error didn’t happen. In development, it would throw an error on the difference between http://localhost or http://127.0.0.1. Make sure what you see in your browser’s URL bar matches what is set in React’s .env file. In /react/auth/config.ts it is configured to use CookieStorage with cognitoUserPooolsTokenProvider and it wants an exact domain match.

There’s more discussion about this error on the AWS Amplify Github.

Deploy React App to AWS

There’s no fancy CI/CD process for this small project, but all we need to do is upload React’s static build files to the S3 bucket made by Terraform.

From the /react directory:

  1. Make a production build: npm run build
  2. Upload dist folder to S3: aws s3 cp --recursive dist/ s3://bucketname

Terraform keeps the S3 Bucket private so you need to access the React app by going through the CloudFront URL. You can get that from terraform output command in the infra directory. CloudFront is configured to use an OAC(Origin Access Controller) which is how the S3 Bucket can stay private but also serve a public website. The S3 Bucket has a policy to only accept traffic from the CloudFront distribution.

CloudFront has SSL enabled so the React application is now sending encrypted requests.