Migrate AWS Cognito User Pools
Code example how to migrate users from one User Pool to another while preserving passwords
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!