Migrate AWS Cognito User Pools

Code example how to migrate users from one User Pool to another while preserving passwords

Ronny Roeller
NEXT Engineering
3 min readDec 10, 2019

--

Amazon Cognito offers a user directory that scales to millions of users at a competitive price. We’ve been using Cognito for the last couple of years and love its simplicity, robustness and pre-integration with other AWS services.

Why migrate User Pools?

There are various scenario that require a migration between two Cognito User Pools:

  • Adding/changing Cognito custom attributes requires to create a new User Pool
  • For a multi-tenant application, you might start out with one User Pool for all tenants and decide later to split the User Pool per tenant, e.g. to allow for SAML/AD integration.

Let Cognito trigger User Migration

The challenge with User Pool migrations is that you can’t copy the passwords when batch exporting and importing users. If you need to preserve passwords (i.e. nearly always), Cognito offers another solution: the User Migration trigger.

The User Migration trigger executes a Lambda function whenever a user isn’t found in the User Pool during login (or password recovery). The Lambda function must return if the user is legit, and if so, Cognito will automatically create the user in the new User Pool.

AWS offers good documentation for this approach including a code example (see here complete code):

exports.handler = (e, context, callback) => {
if (e.triggerSource === 'UserMigration_Authentication') {
// authenticate user with your existing user directory service
const user = authenticateUser(e.userName, e.request.password);
if (user) {
e.response.userAttributes = {
email: user.emailAddress,
email_verified: 'true',
};
e.response.finalUserStatus = "CONFIRMED";
e.response.messageAction = "SUPPRESS";
context.succeed(e);

} else {
// Return error to Amazon Cognito
callback("Bad password");
}
}
...

The authenticateUser method in above code snippet must look up users in the old user database. So, how does this work if the old user database is another Cognito User Pool?

Use a Cognito User Pool as source of truth

The following code snippet first validates the username/password in the old Cognito User Pool and then loads the user data from that User Pool:

async function authenticateUser(username, password) {
const isp = new CognitoIdentityServiceProvider();
// Validate username/password
const resAuth = await isp.adminInitiateAuth({
AuthFlow: 'ADMIN_USER_PASSWORD_AUTH',
AuthParameters: {
PASSWORD: password,
USERNAME: username,
},
ClientId: OLD_CLIENT_ID,
UserPoolId: OLD_USER_POOL_ID,
})
.promise();
if (resAuth.code && resAuth.message) {
return undefined;
}
// Load user data
const resGet = await isp.adminGetUser({
UserPoolId: OLD_USER_POOL_ID,
Username: username,
})
.promise();
if (resGet.code && resGet.message) {
return undefined;
}
return {
emailAddress: resGet.UserAttributes.find(e => e.Name === 'email').Value,
}
;
}

The function could also copy over additional data from Cognito like the preferred username or Cognito custom attributes:

async function authenticateUser(username, password) {
...
// Load user data
const resGet = await isp.adminGetUser({
UserPoolId: OLD_USER_POOL_ID,
Username: username,
}).promise();
if (resGet.code && resGet.message) {
return undefined;
}
const findUserAttribute = name => resGet.UserAttributes.find(e => e.Name === name).Value;
return {
emailAddress: findUserAttribute('email'),
preferred_username: findUserAttribute('preferred_username'),
'custom:tenant': findUserAttribute('custom:tenant'),

};
}
...exports.handler = (e, context, callback) => {
if (e.triggerSource === 'UserMigration_Authentication') {
const user = authenticateUser(e.userName, e.request.password);
if (user) {
e.response.userAttributes = {
email: user.emailAddress,
email_verified: 'true',
preferred_username: user.preferred_username,
'custom:tenant': user['custom:tenant'],

};
e.response.finalUserStatus = "CONFIRMED";
e.response.messageAction = "SUPPRESS";
context.succeed(e);
} else {
// Return error to Amazon Cognito
callback("Bad password");
}
}
...

Looking for a complete code example? We open sourced our sample Lambda function on Github.

Happy coding!

--

--

Ronny Roeller
NEXT Engineering

CTO at nextapp.co # Product discovery platform for high performing teams that bring their customers into every decision