DEV Community

Franz Wong
Franz Wong

Posted on

HowTo: Implement user sign up and login with AWS Cognito

AWS supports user management and authentication with Cognito. You can save user information in Cognito securely.

To focus on Cognito itself, this tutorial will not include express, express-session and passport. Also, we are doing server side authentication instead of client side.

Imagine we have a page to ask user to input email address and password to sign up. No email verification is required.

After user signed up, they can login with the same credential. We don’t ask them to change password on 1st login (because the password is provided by them).

Setup Cognito

First we need to create a User Pool which stores our users. We will name it MyUserPool.

export USER_POOL_ID=$(aws cognito-idp create-user-pool \
  --pool-name MyUserPool \
  --query UserPool.Id \
  --output text)

Next, we need to create a client which can access our User pool. We will name it MyUserPoolClient. We also specify explicit-auth-flows as ADMIN_NO_SRP_AUTH, so we can simply provide username and password to do authentication.

export CLIENT_ID=$(aws cognito-idp create-user-pool-client \
  --user-pool-id $USER_POOL_ID \
  --client-name MyUserPoolClient \
  --explicit-auth-flows ADMIN_NO_SRP_AUTH \
  --query UserPoolClient.ClientId \
  --output text)

Setup IAM

We need to create an IAM user for our server to access Cognito. We name this user as MyCognitoIAMUser and attach managed policy AmazonCognitoPowerUser to it. You should create IAM role instead of IAM user if you run on AWS EC2 or Lambda.

IAM_USER_NAME=MyCognitoIAMUser

aws iam create-user --user-name $IAM_USER_NAME

aws iam  attach-user-policy \
  --user-name $IAM_USER_NAME \
  --policy-arn arn:aws:iam::aws:policy/AmazonCognitoPowerUser

CREDENTIALS=$(aws iam create-access-key --user-name $IAM_USER_NAME \
  --query 'AccessKey.[AccessKeyId,SecretAccessKey]' \
  --output text)

export ACCESS_KEY_ID=$(echo $CREDENTIALS | awk '{print $1}')

export SECRET_ACCESS_KEY=$(echo $CREDENTIALS | awk '{print $2}')

Setup AWS client

We need to install aws-sdk to call Cognito API.

npm i aws-sdk

So now we can start coding. First we need to setup our AWS client first.

const AWS = require('aws-sdk')
// You don't need it if you are running on EC2 or Lambda
if (process.env.NODE_ENV === 'development') {
  AWS.config.update({
    region: process.env['AWS_REGION'],
    accessKeyId: process.env['ACCESS_KEY_ID'],
    secretAccessKey: process.env['SECRET_ACCESS_KEY']
  })
}

Sign up

We first create our signUp function first.

async function signUp(email, password) {
  try {
    const cognito = new AWS.CognitoIdentityServiceProvider()
    await cognito.adminCreateUser({
      UserPoolId: process.env.USER_POOL_ID,
      Username: email,
      MessageAction: 'SUPPRESS',
      TemporaryPassword: password,
    }).promise()
  } catch (err) {
    throw err
  }
}

As you can see, the password is passed as a temporary password. It means user needs to change on 1st login. But we don’t want to do this because the password is already provided by the user. We need to programmatically to change the password for them.

This is how our final version of signUp function looks like.

async function signUp(email, password) {
  try {
    const cognito = new AWS.CognitoIdentityServiceProvider()
    await cognito.adminCreateUser({
      UserPoolId: process.env.USER_POOL_ID,
      Username: email,
      MessageAction: 'SUPPRESS',
      TemporaryPassword: password,
    }).promise()

    const initAuthResponse = await cognito.adminInitiateAuth({
      AuthFlow: 'ADMIN_NO_SRP_AUTH',
      ClientId: process.env.CLIENT_ID,
      UserPoolId: process.env.USER_POOL_ID,
      AuthParameters: {
        USERNAME: email,
        PASSWORD: password
      }
    }).promise()

    if (initAuthResponse.ChallengeName === 'NEW_PASSWORD_REQUIRED') {
      await cognito.adminRespondToAuthChallenge({
        ChallengeName: 'NEW_PASSWORD_REQUIRED',
        ClientId: process.env.CLIENT_ID,
        UserPoolId: process.env.USER_POOL_ID,
        ChallengeResponses: {
          USERNAME: email,
          NEW_PASSWORD: password,
        },
        Session: initAuthResponse.Session
      }).promise()
    }
  } catch (err) {
    throw err
  }
}

Login

Login is much simpler. Our function will return the response from AWS. You can get the access token, ID token, refresh token from the response.

async function login(email, password) {
  try {
    const cognito = new AWS.CognitoIdentityServiceProvider()
    return await cognito.adminInitiateAuth({
      AuthFlow: 'ADMIN_NO_SRP_AUTH',
      ClientId: process.env.CLIENT_ID,
      UserPoolId: process.env.USER_POOL_ID,
      AuthParameters: {
        USERNAME: email,
        PASSWORD: password
      }
    }).promise()
  } catch (err) {
    throw err
  }
}

You can check the schema of the response in here.

{
   "AuthenticationResult": { 
      "AccessToken": "string",
      "ExpiresIn": number,
      "IdToken": "string",
      "NewDeviceMetadata": { 
         "DeviceGroupKey": "string",
         "DeviceKey": "string"
      },
      "RefreshToken": "string",
      "TokenType": "string"
   },
   "ChallengeName": "string",
   "ChallengeParameters": { 
      "string" : "string" 
   },
   "Session": "string"
}

Top comments (2)

Collapse
 
razor1911 profile image
razor1911 • Edited

Thanks for you post Frankz, just one question, How do you validate if the user you are going to create already exists due if you don't check it previously, the creation will fail saying "User account already exists"?

Collapse
 
franzwong profile image
Franz Wong

You can only know a user exists when you get the AliasExistsException.

Perhaps this link can help you.
github.com/aws-amplify/amplify-js/...