Table of Contents
- Introduction
- Starting Point
- Create AWS Infrastructure
- React Application Setup
- Deploy React Application to AWS
- UnexpectedSignInInterruptionException
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.
- Clone the repository with the Terraform files
git clone git@github.com:ryanef/terraform-react-cognito.git
- Change into the directory:
cd terraform-react-cognito
- Change into the infra directory:
cd infra
terraform init
terraform plan
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
.
- Make the script executable
sudo chmod +x envvar.sh
- Run the script:
./envvar.sh
- Verify that
react/.env
andreact/.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.
- Change into react directory:
cd react
- Install React and dependencies:
npm install
- Run a local web server and test React:
npm run dev
- 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:
- Make a production build:
npm run build
- 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.