Verify a Cognito User's Email + [Google, Facebook OAuth]

avatar

Borislav Hadzhiev

4 min

banner

Photo from Unsplash

Table of Contents #

  1. Verify a Cognito User's Email
  2. Verify a Facebook OAuth Email in AWS Cognito
  3. Verifying a Google OAuth Email in AWS Cognito

Verify a Cognito User's Email #

We need to verify a Cognito user's email because otherwise, they can't use the forget password and other functionality.

In order to verify a Cognito user's email, we have to set their email_verified attribute to true. To set their email_verified attribute to true we can use the admin-update-user-attributes command.

shell
aws cognito-idp admin-update-user-attributes --user-pool-id YOUR_USER_POOL_ID --username john@gmail.com --user-attributes Name="email_verified",Value="true"

verify user email

We're using the admin-update-user-attributes command to verify the user's email, however, it can be used to update any of the Cognito user's attributes.

To make sure the email of the user is verified, run the admin-get-user command.

shell
aws cognito-idp admin-get-user --user-pool-id YOUR_USER_POOL_ID --username john@gmail.com --query "UserAttributes"

confirm email verified

The admin-get-user command returns information about the Cognito user, however we've filtered the output to only show the user's attributes, by setting the --query parameter.

Once the user's email_verified attribute is set to true, they can use the forget password functionality and get emails with confirmation codes.

Verify a Facebook OAuth Email in AWS Cognito #

When using Facebook OAuth by default the email_verified attribute is set to false.

By setting the email_verified attribute to true we can use functionality like Forgotten Password for Cognito native (email) accounts that are linked to the Facebook OAuth account.

Verifying Facebook OAuth Emails in Cognito #

According to AWS Support the best way to handle Facebook email verification is by using the Post Authentication Lambda trigger - support comment

AWS Cognito invokes the Post Authentication Lambda trigger after a user signs in. In the function we would have to update the email_verified attribute using the AdminUpdateUserAttributes API.

It's important to note that, as the name Post Authentication suggests, this trigger is run every time the user logs in.

It would be more natural to handle something like this in the Post Confirmation Lambda Trigger, which runs only after a user has successfully been registered. However, that wouldn't work because you would get a race condition

Let's see how we can implement the Post Authentication Lambda trigger.

Code for the Post Authentication Lambda Trigger #

Let's define the email confirmation lambda function:

import AWS from 'aws-sdk'; import {Callback, Context, PostAuthenticationTriggerEvent} from 'aws-lambda'; import {adminUpdateUserAttributes} from './admin-update-user-attributes'; const adminUpdateUserAttributes = async ({ userPoolId, username, }: { userPoolId: string; username: string; }): Promise<AWS.CognitoIdentityServiceProvider.AdminUpdateUserAttributesResponse> => { const params = { UserPoolId: userPoolId, Username: username, UserAttributes: [{Name: 'email_verified', Value: 'true'}], }; const cognitoIdp = new AWS.CognitoIdentityServiceProvider(); return cognitoIdp.adminUpdateUserAttributes(params).promise(); }; export async function main( event: PostAuthenticationTriggerEvent, _context: Context, callback: Callback, ): Promise<void> { const {userPoolId, userName} = event; console.log('POST CONFIRMATION EVENT', JSON.stringify(event, null, 2)); if (event.request.userAttributes.email) { const identities = event.request?.userAttributes?.identities; const isExternalUser = /providername.*facebook/gi.test(identities) || /providername.*google/gi.test(identities); if (isExternalUser) { try { await adminUpdateUserAttributes({ userPoolId, username: userName, }); return callback(null, event); } catch (error) { console.log( 'POST AUTHENTICATION ERROR: ', JSON.stringify(error, null, 2), ); return callback(error, event); } } } return callback(null, event); }

We basically check if the user who's signing in is External (i.e. Facebook or Google - you can only check for the one you use).

If the user is external, we call adminUpdateUserAttributes, which sets the email_verified property to true.

If the user is not an external one, we just return the callback, without doing anything.

The Lambda trigger requires the cognito-idp:AdminUpdateUserAttributes action to call the necessary API.

This policy grants the necessary permissions:

policy.json
{ "Version": "2012-10-17", "Statement": [ { "Action": "cognito-idp:AdminUpdateUserAttributes", "Resource": "YOUR_USER_POOL_ARN", "Effect": "Allow" } ] }

After you've defined the Lambda function you can set it as a Post Authentication Lambda trigger in your User Pool.

post authentication trigger

Discussion #

This is definitely one of the rough edges around Cognito as it doesn't make much sense to confirm a user's email every time they log in to your application.

However, this is the current way to handle this and at least in my implementation I haven't experienced any issues.

Verifying a Google OAuth Email in AWS Cognito #

When you use AWS Cognito and Amplify with Google OAuth user emails are not automatically verified.

In other words, the email_verified attribute is set to false for Google registered users.

We need users to verify their email because otherwise, we can't use the forgot password functionality. This is very important in case you support Cognito native email login and link the accounts.

In order to verify the emails of Google OAuth accounts in Cognito, we have to provide an attribute mapping between Google's email_verified attribute and Cognito's email_verified attribute.

After the email_verified attribute has been mapped between Google and Cognito, we can leverage the existing email_verified property on the user's Google account.

Here's how the attribute mapping looks in the AWS Console:

email verified mapping

The following snippet shows how to set the attribute mapping in AWS CDK:

this.identityProviderGoogle = new cognito.UserPoolIdentityProviderGoogle( this, 'userpool-identity-provider-google', { // ... other config attributeMapping: { email: { attributeName: cognito.ProviderAttribute.GOOGLE_EMAIL.attributeName, }, custom: { email_verified: cognito.ProviderAttribute.other('email_verified'), }, }, }, );

Discussion #

Once we map the email_verified attribute we can leverage the existing property on the user's google account.

The most intuitive way would be if the email_verified property of Google accounts were set to true by default, but that's currently not the case.

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.