sorenandersen.com

Manage user profile data between Cognito and DynamoDB

January 11, 2021

When dealing with user profiles in serverless applications we often turn to Cognito for managing their credentials while the app itself will store user entities and their data related to the respective business domain. By doing so we retain easy and fast access to user profiles within our app code while keeping the schema extensible as the application grows.

Enhancing User Pool workflows with Lambda triggers

Cognito User Pools lets us customize workflows by triggering Lambda functions when certain events happen during sign-up and authentication operations. There are plenty of hooks to tap into and for persisting user entities the Post Confirmation trigger is a sensible place to act on new users signing up.

alt text

In above diagram the Cognito-triggered Lambda simply publishes a business event to EventBridge containing the new users id, username, email and name as registered in Cognito. This is a low-risk operation that quickly and frictionless returns control back to the Cognito workflow while ensuring subscribed listeners to be invoked asynchronously with all of AWS’ built-in robustness in place.

In our scenario EventBridge will now invoke the ebCreateUser Lambda handler which persists the user in DynamoDB. As depicted the same event can be used to propel other use cases such as sending a welcome email or other initiatives to greet or nudge new users of our app.

Serverless Framework setup

The Serverless Framework fully supports the configuration of Cognito User Pool triggers:

functions:
  cognitoPostConfirmation:
    handler: src/handlers/cognito/postConfirmation.handler
    events:
      - cognitoUserPool:
          pool: RossUserPool
          trigger: PostConfirmation

Avoid duplicate User Pools

Be aware though that correct naming of the User Pool is of great importance, or the framework will create a new pool. Michael Kiley wrote a great article on how to do this. The docs also has a section on this, describing it as overriding a generated User Pool.

It is worth noticing that this approach requires the User Pool and Lambda function to reside in the same CloudFormation stack.

Lambda handler code

The Post Confirmation trigger is also invoked in forgot password workflows, so remember to guard your business logic with a check on the trigger source:

module.exports.handler = async (event) => {
  if (event.triggerSource === 'PostConfirmation_ConfirmSignUp') {
    // Handler code goes here ...
  }

  // Return the event object
  return event;
}

The Lambda handler must return the event object as Cognito expects it when control is returned to the built-in workflows.

User status changes not triggering the Post Confirmation event

Beware user status changes to CONFIRMED that does not invoke the Post Confirmation trigger.

This is the case for users starting in the FORCE_CHANGE_PASSWORD state, e.g. when created programmatically with the adminCreateUser SDK method. They will, despite transitioning to the CONFIRMED state after performing a password change, not see a Post Confirmation trigger invocation. This official forum thread sheds light on why this is the case.

In automated tests we can instead create users with metods signUp and adminConfirmSignUp, which will ensure the Post Confirmation trigger to be invoked.

Configuring Cognito User Pools for case insensitive usernames

For web applications it’s generally the norm to use case-insensitive logins and Amazon also recommends to enable case insensitivity for username input. While new User Pools will be set to case insensitive by default when created in the console, we are still required to specify this behaviour explicitly when creating User Pools programmatically.

As documented here we can achieve this with the following snippet of CloudFormation:

CognitoUserPoolRossUserPool:
  Type: AWS::Cognito::UserPool
  Properties:
    ...
    UsernameConfiguration:
      CaseSensitive: False

The provided configuration also applies to email addresses when allowing users to use their email address as username to sign up and sign in.

Conclusion

In this post we saw how to use Cognito, Lambda and DynamoDB in cooperation to effectively manage user profiles. Cognito’s built-in and extensible flows makes this easy once aware of a few pitfalls as presented here.


Søren Andersen

Digital garden of Søren Andersen.
Posts on tech that I use and learn.