React & Cognito

We use AWS Amplify to connect AWS to Cognito, and on top of this challenge we also had to have three environments: Local, Staging and Production.

Requirement Libraries

We needed to add two libraries, one is aws-amplify and the other is env-cmd to manage our different environment and environment variables within React.

First, aws-amplify:

$ npm install aws-amplify --save

Then, env-cmd:

$ npm install env-cmd --save

React

In the current theme we are using, we first needed to manage our environments. To do this, I’ve created a file called .env-cmdrc in the react root directory (the same directory package.json lives in), and it looks like this:

{
  "local": {
    "REACT_APP_AWS_COGNITO_REGION": "us-east-1",
    "REACT_APP_AWS_COGNITO_USER_POOL_ID": "us-east-1_U2dzkxfTv",
    "REACT_APP_AWS_COGNITO_APP_CLIENT_ID": "3u9n9373e37v603tbp25gs5fdc"
  },
  "staging": {
    "REACT_APP_AWS_COGNITO_REGION": "us-east-1",
    "REACT_APP_AWS_COGNITO_USER_POOL_ID": "us-east-1_U2dzkxfTv",
    "REACT_APP_AWS_COGNITO_APP_CLIENT_ID": "3u9n9373e37v603tbp25gs5fdc"
  },
  "production": {
    "REACT_APP_AWS_COGNITO_REGION": "us-east-1",
    "REACT_APP_AWS_COGNITO_USER_POOL_ID": "us-east-1_Zc3pNWX51",
    "REACT_APP_AWS_COGNITO_APP_CLIENT_ID": "ins01e2a8d3vd8apvnd0jv10c"
  }
}

As you can see, it contains the three environments that we need; however, the local and staging environments are identical. I left them both there, because there may be instances when we want to modify the local environment later on. Why are they identical? Because we are following the same patterns we followed in Vision Zero, where the VZE (the editor) when it runs locally it connects to the Staging environment.

The Start/Build Scripts

I had to refactor the commands we use in the scripts section of package.json, so that we could specify the environments we have to load for the current session or build.

We use the command env-cmd -e [environment] command to specify what environment we want to load from the .env-cmdrc json file:

"scripts": {
    "start": "env-cmd -e local npm run start:local",
    "start:local": "export HTTPS=true && react-app-rewired start",
    "start:staging": "env-cmd -e staging npm run start:local",
    "start:production": "env-cmd -e production npm run start:local",
    "build": "env-cmd -e local npm run build:local",
    "build:local": "GENERATE_SOURCEMAP=false react-scripts build && cp _redirects build && cp -r build moped && mv moped build",
    "build:staging": "env-cmd -e staging npm run build:local",
    "build:production": "env-cmd -e production npm run build:local",
    [...]

Start:

  • start This command activates the local environment and runs start:local

  • start:local is the center of it all, it contains the commands to start the react app.

  • start:staging activates the staging environment and runs start:local

  • start:production activates the production environment and runs start:local

  • build activates the local environment and runs build:local

  • build:local is at the center, it contains the commands it needs to build the react app.

  • build:staging activates the staging environment and runs build:local

  • build:production activates the production environment and runs build:local

AWS-Amplify & The Matx Theme

To use aws-amplify with the Matx theme, we had to make some adjustments. First, we must be aware that the theme makes use of React-Redux, and not in a complex manner, it only makes use of it in a simple way.

1. The configuration file

Initially, I first created a configuration file in the ./src directory, it looks like this:

export default {
    apiGateway: {
        REGION: "us-east-1",
        URL: "https://tm4r2xqz3c.execute-api.us-east-1.amazonaws.com/dev/hello"
    },
    cognito: {
        REGION: process.env.REACT_APP_AWS_COGNITO_REGION,
        USER_POOL_ID: process.env.REACT_APP_AWS_COGNITO_USER_POOL_ID,
        APP_CLIENT_ID: process.env.REACT_APP_AWS_COGNITO_APP_CLIENT_ID,
    }
};

Please notice that this is the first initial state and not its final, this is only to demonstrate how the configuration loads the necessary variables for interaction with cognito:

  • REGION

  • USER_POOL_ID

  • APP_CLIENT_ID

2. Include AWS Amplify in the index page

In the index page (src/index.jsx), aws-amplify has been included as follows:

// Includes the library:
import Amplify from "aws-amplify";
[...]
// Imports the configuration above:
import config from "./config";
[...]

After the library and configuration details have been imported, we can proceed to initialize it. In the same file, we write the following:

Amplify.Logger.LOG_LEVEL = 'DEBUG';

Amplify.configure({
    Auth: {
        mandatorySignIn: true,
        region: config.cognito.REGION,
        userPoolId: config.cognito.USER_POOL_ID,
        identityPoolId: config.cognito.IDENTITY_POOL_ID,
        userPoolWebClientId: config.cognito.APP_CLIENT_ID
    },
    API: {
        endpoints: [
            {
                name: "testApi",
                endpoint: config.apiGateway.URL,
                region: config.apiGateway.REGION
            }
        ]
    }
});

Again, this is not permanent code and this only serves as a demonstration of how the code can work. It should be also helpful to mention that LOG_LEVEL is optional, it’s there for us to see what the library is doing and it may be helpful when debugging in local or staging development.

3. The jwtAuthService.js file

The Matx theme makes use of React-Redux, and its patterns make use of a file called src/app/services/jwtAuthService.js. This file controls the gathering of a token and creating a user session.

It’s a class that has a few methods, one of them is called loginWithEmailAndPassword and its code is something like this:

loginWithEmailAndPassword = (email, password) => {
    return Auth.signIn(email, password).then(data => {
      localStorage.setItem("atd_aws_cognito_session", JSON.stringify(data));
      localStorage.setItem("atd_aws_cognito_session_payload", JSON.stringify(data.signInUserSession.idToken.payload));
      this.user = this.buildUserData(data);
      this.setSession(this.user.token);
      this.setUser(this.user);
      return this.user;
    }).catch((error) => {
      console.log(error);
      alert(error.message);
    });
  };

This method retrieves the the email and password, and notice the use of the class/method Auth.signIn which is part of the AWS-Amplify library. In here, we basically pass the same values to the library as we receive them. That method returns a promise, which allows us to implement whatever functionality we need. In this case, we call a few methods to build the session and to store the response we get back from Cognito. If there is an error, we currently show it as an alert dialog. All the code above is bound to be modified as we are in the early stages of implementation.

The theme expects the session to have a specific format, for this purpose I created a method called buildSession which takes the data we received from Cognito and creates a new object with the values the theme is expecting, currently it looks like this:

buildUserData = (awsCognitoData) => {
  return {
    role: "ADMIN",
    userId: awsCognitoData.username,
    username: awsCognitoData.username,
    email: awsCognitoData.username,
    token: awsCognitoData.signInUserSession.idToken.jwtToken,
    tokenPayload: JSON.stringify(awsCognitoData.signInUserSession.payload),
    displayName: "Authenticated User",
    photoURL: "/assets/images/face-6.jpg",
    age: 0,
  }
}

I expect the code to change soon, but as of the time of this writing that is what it looks like. Notice the role, username, email, photoUrl, and age fields as an example of what is required. We need to explore the theme to see what we really need, and if it is work expanding the functionality of Cognito to retrieve the extra fields or if we should retrieve those from the database.

Team

Informed

  • @ Stakeholder

  • @ Stakeholder

Status

Last date updated

e.g.,24 Sep 2020

On this page

Name

Description

Operational Excellence

The ability to run and monitor systems to deliver business value and to continually improve supporting processes and procedures.

Security

The ability to protect information, systems, and assets while delivering business value through risk assessments and mitigation strategies.

Reliability

The ability of a system to recover from infrastructure or service disruptions, dynamically acquire computing resources to meet demand, and mitigate disruptions such as misconfigurations or transient network issues.

Performance Efficiency

The ability to use computing resources efficiently to meet system requirements, and to maintain that efficiency as demand changes and technologies evolve

Cost Optimization

The ability to run systems to deliver business value at the lowest price point.

AWS Well Architected Framework PDF

note

Goals

  • We need to be able to log in with username and password to Cognito

  • We need to be able to retrieve a token from cognito

  • We need to build a session with the details provided by cognito

Goals

  • We need to be able to log in with username and password to Cognito

  • We need to be able to retrieve a token from cognito

  • We need to build a session with the details provided by cognito

Architecture

Architecture flow

The flow is straight forward, and there is little complexity.

  1. Initialize the environment variables containing the user pool id, etc.

  2. Initialize the amplify class instance (in index.js)

  3. Let jwtAuthService run whenever the user logs in, and build a session.

  4. Utilize the session data as needed.

Other notes:

There needs to be some understanding of how the sessions data will be utilized in a granular way, as well as using it with routes.

Deployment strategy

The react application is built using Netlify. Netlify was configured to deploy based on two specific branches: main and production. Each branch has separate environment variables, which are located in the .env-cmdrc file, these variables are applied in the build command as stipulated above.

References and documentation

Last updated

Was this helpful?