AWS Cognito Passwordless Implementation: For Bonus, add Hasura Claims in the Token.
Tags: javascript, beginners, tutorial, devops
Published: 2022-02-09 12:18:13

Hey everyone,

In today's tutorial, I will be guiding you in implementing passwordless authentication flow with AWS Cognito. I will be using AWS CDK to provision the backend. Using AWS SDK will provide us with an excellent way of cleaning up all the resources we provision once done with the tutorial. Here is an article I wrote about [Getting Started with AWS SDK](https://dev.to/kevin_odongo35/getting-started-with-aws-cdk-2k19)

With this approach, there are a few things we need to discuss before proceeding. Have you used Cashup before and seen how their authentication flow works?. In Cashup,  users can log in using their email or phone number. There is no password to remember;  a code is sent to them using the phone number or email they provided on registration. Super cool indeed. 

<img height="100%" width="100%" src="https://media.giphy.com/media/jTByV2aZDFvuU/giphy.gif">

We need to use an email provider and SMS provider to send the code to users in this implementation. You can leverage AWS SES, AWS SNS, and AWS Pinpoint or use any third-party provider like Twilio, etc. To begin with, let me show you how to configure AWS SES, AWS SNS, and AWS Pinpoint.

## Configure AWS SES
Log into AWS SES [dashboard](https://us-east-2.console.aws.amazon.com/ses/home?region=us-east-2#/homepage) and click Create Identity

![AWS SES Dashboard Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p0egtnouac6pig6lpo29.png)

Provide an email address and click create identity button. Ensure you have access to this email address because AWS will send a link to verify the email address.

![AWS SES Create Identity](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/35spzp93ah0d3qfdp3sa.png)

Once you verify the email go to the list of all the verified identities, you should see your email as verified:


![List of Verified Identities](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/diguv02ok372rfqt45p9.png)

Note that your account will be in Sandbox, and you can only send emails to verified identities until you are out of the sandbox. 
 
## Configure AWS Pinpoint

AWS Pinpoint is not available in all regions of AWS. Log into AWS Pinpoint [dashboard](https://console.aws.amazon.com/pinpoint/home?region=us-east-1#/about) and click manage projects and create a new project:

![AWS Pinpoint dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n3gbmdyodokrx3113xr2.png)

Once you provide the project name, select the feature you want to enable, in this case, SMS and voice. Take note of the projectId when the project is created.

![Feature selection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h12sqxskx7qsgy281v6x.png)

## Configure SNS
Log into AWS SNS [dashboard](https://console.aws.amazon.com/sns/v3/home?region=us-east-1#/mobile/text-messaging) and click on Text messaging (SMS) 

![AWS SNS dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c0pdnqmmwfioljrvzv19.png)

Add a phone number and ensure you verify the phone number. Note that your account will be in Sandbox, and you can only send SMS to verified phone numbers until you are out of the Sandbox. 

![Verified phone numbers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/60xz7cnv6w28e2nr9whe.png)

Setting up AWS email and SMS providers is quick and easy. You can use any provider of your choice. The whole task is to deploy the AWS Cognito and implement a passwordless authentication flow. I have created a simple front-end application that implements this; I have shared the repo in the front-end section. Clone the backend [https://github.com/kevinodongo/aws-cognito-passwordless-cdk-deployment.git](https://github.com/kevinodongo/aws-cognito-passwordless-cdk-deployment.git) and run it locally: 

```javascript
//clone and 
git clone https://github.com/kevinodongo/aws-cognito-passwordless-cdk-deployment.git

//cd in the application
cd aws-cognito-passwordless-cdk-deployment
// install all dependecies
yarn install

// run the application
// deploy the backend and update evn.local file
yarn run dev
```


To deploy the backend, run the following commands:

```javascript
// This lambda requires third-party dependencies to be installed before being deployed.
cd  lambda/create-auth-challenge
yarn install

// go back to the root of the file
// in the folder bin/backend.ts provide the source email you registered and // the projectId of the AWS Pinpoint project you created.

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { BackendStack } from '../lib/backend-stack';

const prodRegion = 'us-east-2' /*Your recources will be deployed in this region*/
const hasuraClaims = false /*required incase using AWS Cognito with Hasura*/

// With AWS Pinpoint in US you have to provide origination number
// Update the createAuthChallenge lambda with origination number

const pinpointApplicationId = "" /*required*/
const originationNumber = "" /*required only when sending SMS in US*/
const sourceEmail = "" /*required*/

const app = new cdk.App();

new BackendStack(app, 'BackendStack', {
  pinpointApplicationId: pinpointApplicationId,
  hasuraClaims: hasuraClaims,
  sourceEmail: sourceEmail,
  originationNumber: originationNumber,
  env: {
    region: prodRegion
  }
});
```

On completion updating the `backend.ts` file, run the following commands:

``` javascript
// build the application
npm run build

// synthesis the application
cdk synth

// incase it is your first time you might get an error. Run the following command:
cdk bootstrap

// Deploy the backend application
cdk deploy --profile ***

```
Take note of the output emitted when deployment is complete; you will get the AWS Cognito UserPool Id and UserPool Web Client Id. Save these somewhere, as you will need them to configure the front-end.

![AWS CDK deployment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1je4smcdg1ocsihuxjh0.png)

## BONUS - How to add Hasura claims in the token. 

We have added a lambda function that will add Hasura claims to the token generated by AWS Cognito. To enable this add true to hasuraClaims props in the following file /bin/backend.ts. That is all you have do. The tokens generated will hasura claims as follows:


![Token with hasura claims](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bs3vi7cj4cf8ioq3nwlr.png)

This step is essential because when you integrate Hasura with AWS Cognito; when you are making queries and mutations, the Hasura engine will verify each request by checking on the claims in the token.

```javascript
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { BackendStack } from '../lib/backend-stack';

const prodRegion = 'us-east-2' /*Your recources will be deployed in this region*/

// change this to true, build, synthesize and deploy the application.
const hasuraClaims = true /*required in case using AWS Cognito with Hasura*/
...
```

![Cloud Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2r9jg3whvebfoerzplr8.jpg)

## Front-end implementation
My example is built using the Next.js framework, but not everybody uses Next.js. The first thing you need to do is install AWS amplify library. Get my example [here](https://github.com/kevinodongo/aws-cognito-passwordless-next-js.git)

```
// install amplify library
yarn add aws-amplify

// clone example
git clone https://github.com/kevinodongo/aws-cognito-passwordless-next-js.git
cd aws-cognito-passwordless-next-js
yarn install
npm run dev
```
Create a folder called lib (Name the folder or files any names there is no convention about the naming) file called `auth.ts` and `utils.ts`. In `auth.ts` add the following content:


```javascript
// AWS Amplify 
import { Auth } from 'aws-amplify';
import { getRandomString } from "./utils"

export const checkUserSession = async () => {
  try {
    const user = await Auth.currentAuthenticatedUser()
    return user
  } catch (error) {
    console.log(error);
  }
}

export const signIn = async (username: string) => {
  try {
    const user = await Auth.signIn(username);
    return user
  } catch (error) {
    throw new Error("Please check on username or password")
  }
}

export const signUp = async (username: string) => {
  let userAttributes = null
  let emailRegex = new RegExp(/[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+/)
  if (emailRegex.test(username)) {
    userAttributes = {
      email: username,
      phone_number: ""
    }
  } else {
    userAttributes = {
      email: "",
      phone_number: username
    }
  }

  try {
    const { user } = await Auth.signUp({
      username: username,
      password: getRandomString(30),
      attributes: userAttributes
    });
    return user
  } catch (error) {
    throw new Error("Something wrong occured when we were creating your account")
  }
}

export async function answerCustomChallenge(cognitoUser: string, code: string) {
  try {
    const answerResponse = await Auth.sendCustomChallengeAnswer(cognitoUser, code)
    console.log('authresponse', answerResponse)
    return answerResponse
  } catch (error) {
    console.log('Apparently the user did not enter the right code', error);
  }
}

export const signOut = async () => {
  try {
    await Auth.signOut();
  } catch (error) {
    console.log(error);
  }
}

export const globalSignOut = async () => {
  try {
    await Auth.signOut({ global: true });
  } catch (error) {
    console.log(error);
  }
}

```

In `utils.ts` add the following contents:

```javascript
export function getRandomString(bytes: number) {
  const randomValues = new Uint8Array(bytes);
  window.crypto.getRandomValues(randomValues);
  return Array.from(randomValues).map(intToHex).join('');
}

function intToHex(nr: number) {
  return nr.toString(16).padStart(2, '0');
}
```

In `main.js`, `index.js` or `app.js`, this depends on the framework you are using. Initialize AWS Amplify as follows. 

``` javascript
// intialize amplify
import { Amplify } from "aws-amplify";
Amplify.configure({
  Auth: {
    region: process.env.NEXT_COGNITO_REGION,
    userPoolId: process.env.NEXT_USERPOOL_ID,
    userPoolWebClientId: process.env.NEXT_USERPOOLWEBCLIENT_ID
  }, ssr: true
});
```

That is all you have to do; now you can call the functions as follows: 

```javascript
import { signUp, signIn, answerCustomChallenge} from "../auth"

let userResponse
// handle new user
const handleNewUser = async () => {
  // incase you are using phone number include the country prefix +1*******
   let username = /*phone number or email*/
   await signUp(username)
}
// handle login
const handleLogin = async () => {
   let username = /*phone number or email*/
  userResponse = await signIn(username)
}

// handle code
const handleCode = async () => {
   const answeResponse= await  answerCustomChallenge(userResponse, code)
}
```
Congratulations, you have successfully implemented a passwordless authentication flow with AWS Cognito. I will share how to deploy and configure Hasura standalone in ECS Fargate. Hey do not forget to cleanup using the following command:

```
cdk destroy
```

Thank you and see you next time.