DEV Community

Cover image for AWS Cognito Passwordless Implementation: For Bonus, add Hasura Claims in the Token.
Kevin Odongo
Kevin Odongo

Posted on

AWS Cognito Passwordless Implementation: For Bonus, add Hasura Claims in the Token.

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

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.

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 and click Create Identity

AWS SES Dashboard Console

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

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

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 and click manage projects and create a new project:

AWS Pinpoint dashboard

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

Configure SNS

Log into AWS SNS dashboard and click on Text messaging (SMS)

AWS SNS dashboard

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

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 and run it locally:

//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
Enter fullscreen mode Exit fullscreen mode

To deploy the backend, run the following commands:

// 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
  }
});
Enter fullscreen mode Exit fullscreen mode

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

// 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 ***

Enter fullscreen mode Exit fullscreen mode

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

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

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.

#!/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*/
...
Enter fullscreen mode Exit fullscreen mode

Cloud Image

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

// 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
Enter fullscreen mode Exit fullscreen mode

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:

// 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);
  }
}

Enter fullscreen mode Exit fullscreen mode

In utils.ts add the following contents:

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');
}
Enter fullscreen mode Exit fullscreen mode

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

// 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
});
Enter fullscreen mode Exit fullscreen mode

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

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)
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Thank you and see you next time.

Top comments (1)

Collapse
 
mananaemmanuel profile image
MananaEmmanuel

Hi, Kevin, great article!

I have noticed that some of scripts are not included in root package.json file and as such are not working. i.e the npm run dev script is not referenced anywhere.