Samy's blog

Protecting your resources with AWS Cognito and API Gateway

In this post, I will try to explain in a series of steps how to protect your resources ( may it be a REST API or anything else ) using AWS Cognito and API Gateway.

Prerequisites

Creating a User Pool

Step 1 - Get into the AWS console panel ( and log in if prompted to do so )

click here => AWS Management Console

Step 2 - Proceed to the Amazon Cognito Service

Cognito Service link

Step 3 - Click on [Manage User Pools]

Cognito Service link

Now we need to create a User pool so that the users of our application can sign-in and sign-up.

Step 4 - Click on [Create a user pool]

Cognito Service link

Step 5 - Name your pool

Cognito Service link

Step 6 - Configuring the multiple settings

Cognito Service link

On the [Attributes] tab when deciding how the users will sign in I selected [Email address or phone number] but is up to the reader to determine which one it prefers, this will allow the users to sign up using their email as "username." Cognito Service link

On the [Policies] tab things like password strength and temporary password expiration times can be configured. Cognito Service link

On the [Message customizations] tab you can edit things like the message the user will receive after signing up with the app or from which email address you will send the email to the user.

Leave the remaining tabs except for App Client as default.

Step 7 - Adding an App Client

We need to add an App client to our user pool, so on the [App clients] tab click on Add an app client then Add a name to your app client and make sure Generate client secret is unchecked then click on [Create app client]

Cognito Service link

Step 8 - Reviewing Changes and Finally creating the user pool

After creating the App click on the Review tab to review the configuration of the pool Cognito Service link

Cognito Service link

if everything is set and done click on create pool at the bottom. Cognito Service link

Testing our Cognito User Pool

Prerequisites

Cognito Service link

Cognito Service link

Now that we have our Cognito User pool created we can start testing the signup and sign-in method as well as the confirm registration, for this guide I will use the Amazon Cognito Identity SDK for JavaScript, below a quick code snip with some of the most relevant functions that we will need.

/*
  This library uses the Fetch API. 
  For older browsers or in Node.js, 
  you may need to include a polyfill.
*/
global.fetch = require('node-fetch');

const AmazonCognitoIdentity = require("amazon-cognito-identity-js");

// Initialize the user pool, with the user pool ID and the client ID, 
// both found on the user pool that was created
const userPool = new AmazonCognitoIdentity.CognitoUserPool({
    UserPoolId: "Your User Pool id",
    ClientId: "Your user pool App client id"
});
/*
 we can use the signUp method to let users sign up for the app. 
 The user's information can be collected and used to populate 
 CognitoUserAttribute objects that are passed in the signUp call.
*/
const signUp = async (Username, password) => {
    const attributeList = [];

    const dataEmail = {
      Name: "email",
      Value: Username
    };
    const attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(
      dataEmail
    );
    attributeList.push(attributeEmail);

    return new Promise((resolve, reject) => {
      userPool.signUp(Username, password, attributeList, null, (err, result) => {
        if (err) {
          return reject(err.message || JSON.stringify(err));
        }
        const cognitoUser = result.user;
        resolve(cognitoUser);
      });
    });
  };
/*
After signing up, the user confirms the sign-up 
by entering a code sent either through SMS or email 
(based on the user pool settings).To confirm sign-up, 
you must collect the code (confirmationCode in the following example) 
received by the user and use it as follows.
*/
const confirmRegistration = async (Username, confirmationCode) => {
  const userData = {
    Username: email,
    Pool: userPool
  };

  const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);

  return new Promise((resolve, reject) => {
    cognitoUser.confirmRegistration(confirmationCode, true, function(
      err,
      result
    ) {
      if (err) {
        return reject(err.message || JSON.stringify(err));
      }

      resolve(result);
    });
  });
};
/*
A confirmed user signs in to obtain a session. 
The session contains an ID token, an access token, and a refresh token, 
we are picking the ID token in this case, 
as it will play a part when we integrate API Gateway 
in combination with Cognito 👀.
*/ 
const signIn = async (Username, Password) => {
  const authenticationData = {
    Username, // your username here
    Password
  };
  const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
    authenticationData
  );
  const userData = {
    Username,
    Pool: userPool
  };
  const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);

  return new Promise((resolve, reject) => {
    cognitoUser.authenticateUser(authenticationDetails, {
      onSuccess: result => {
        const idToken = result.getIdToken().getJwtToken();
        resolve(idToken);
      },
      onFailure: err => {
        reject(err.message || JSON.stringify(err))
      }
    });
  });
};

Our account registration flow would look something like this:

Cognito Service link

if an account was created and confirmed you should see the newly created account under [User and groups] of your Cognito User Pool, with the status CONFIRMED, or UNCONFIRMED if the user just signed up but hasn't verified his account yet.

Cognito Service link

so far we have a managed pool of users under AWS Cognito, next is how can our signed-up users make use of their accounts to access resources.

Using AWS API Gateway and AWS Cognito to protect our resources.

Prerequisites

Step 1 - Get into the AWS console panel ( and log in if prompted to do so )

click here => AWS Management Console

Step 2 - Proceed to the Amazon API Gateway Service

Cognito Service link

Step 3 - Creating a new API

once on API GatewayAPI Gateway we create a new API by clicking [Create API].

Cognito Service link

then proceed to fill the required fields.

Cognito Service link

Step 4 - Adding a proxy resource to our API

We now need to create a proxy resource; the proxy resource will forward any requests to an endpoint/resource of our choice.

Cognito Service link

Cognito Service link

Cognito Service link

Step 5 - Testing our (yet to be protected) Proxy resource

We can now test if our new proxy resource is forwarding our requests correctly, API Gateway gives us an easy way to test it.

Cognito Service link

Cognito Service link

Cognito Service link

Step 6 - Adding Cognito as Authorizer to our new proxy resource

So far our new endpoint is working, but it's not yet protected, we are now going to add an Authorizer which will enable us to control the access to the resource.

Click on Authorizers on the side menu ( the one under your API name)

Cognito Service link

Then click on [Create New Authorizer]

Cognito Service link

Cognito Service link

Step 7 - Using our new Authorizer with our proxy endpoint

Back on Resources, on our ANY method, we click on [Method Request]

Cognito Service link

Under Authorization, select your recently created Authorizer.

Note: if the authorizer doesn't appear in the list try to refresh the page or logging out/in of AWS console (this is some weird bug 🤷‍♀️)

Cognito Service link

Step 8 - Deploying and testing our (now protected) resource

To properly test if our Cognito Authorizer is working we first need to Deploy our API, under actions, click on [Deploy API]

Cognito Service link

Under [Stages] Cognito Service link

We should now have our protected Invoke endpoint. 👍

Cognito Service link

To test our new protected endpoint, we can try with a simple fetch call from the browser or a request client like Postman.

const url ="your protected Invoke endpoint."
const IdToken = 'the id token that the signIn method returns after the user signs in.'
fetch(url,{
    mode: 'cors',
  headers:{ Authorization: IdToken 
  }
}).then(r => r.json() ).then(console.log)

//failing to send a valid Id Token will result in a 401 response (Unauthorized)

That's it; Amazon Cognito now manages our user pool, it also validates the credentials upon signing in, without us worrying about manually checking it ourselves or losing sleep over our servers getting hacked 👀, I hope you enjoyed this guide as I enjoyed making it.

Happy coding.

ko-fi