Cognito Migration Guide
Note
If you want to keep AWS Cognito as the main identity layer and use Descope for authentication (e.g. passkeys or modern methods), you can configure Descope as an OIDC identity provider in Cognito.
You can migrate from AWS Cognito to Descope in two main ways:
- Full Migration (export users and import into Descope)
- JIT Migration (provision users on sign-in).
You can also use SSO migration for seamless migration of your SSO connections.
AWS Cognito and Descope Terminology
The table below shows how authentication and authorization concepts in AWS Cognito map to Descope.
| AWS Cognito | Descope |
|---|---|
| User Pool | Project - your Descope project and configuration boundary. |
| User (identity in the user pool) | User - identity record with login IDs, profile, and custom attributes. |
| Hosted UI or Custom authentication flow | Flow - the sign-up/sign-in or authentication journey you design in Descope. |
| Identity Provider (social, SAML, or OIDC in Cognito) | Custom OAuth Providers or Tenant-based SSO. |
| App Client (application registered in user pool) | Your Descope Inbound Application. |
| User attributes (standard and custom) | User custom attributes and built-in user fields (email, name, phone, etc.). |
| User Groups | Roles in Descope (map Cognito groups to Descope roles). |
| Lambda Triggers (pre/post authentication, etc.) | Flow steps, Connectors, and webhook actions in Descope flows. |
| MFA (SMS, TOTP, software token) | MFA in flows - TOTP, SMS OTP, passkeys, authenticator apps. |
AWS Cognito organizes users into user pools.
Enterprise IdPs (SAML/OIDC) are external identity providers in that pool. User groups provide basic authorization; advanced authorization requires custom Lambda functions or custom attributes.
If you use Descope Tenants or Descope Roles, you are introducing those models yourself — there is nothing in Cognito to map 1:1 unless you used custom attributes or external systems.
You will have to decide how to translate your setup (e.g. one Descope tenant per Cognito user pool, Cognito groups mapped to Descope roles, etc).
Full Migration
Passwords cannot be exported from AWS Cognito
AWS Cognito does not allow the export of user password hashes. This means a full migration is always a without-passwords migration.
In a full migration you move your users and identity data to Descope and eventually run only on Descope.
After importing users into Descope, you have two options. You can either transition users to passwordless authentication methods (magic link, OTP, passkeys), or require users to reset their password on first sign-in (e.g. using a freshlyMigrated flag to trigger a password-reset step in your flow).
If preserving the existing password experience is critical, consider JIT migration instead.
Getting the Existing User Data
The recommended approach is to use the Descope Migration Tool — either out of the box or modified for your needs. The tool imports users and custom attributes from your Cognito User Pool and, if you use Cognito User Groups, automatically converts them to Descope roles and assigns users to those roles.
For full control over the migration process, you can instead export users from the Cognito API with the boto3 client and build your own import pipeline.
Using the Descope Migration Tool
You can use the Descope Migration Tool to handle much of the work for you. Migrations can differ wildly depending on the specific identity implementation required. However, this tool acts as a template for which you can edit, depending on your needs.
It contains the boto3 SDK initialization, and the basic user/groups import functionality into Descope.
This tool will perform the following actions:
- Configure your Cognito User Groups as Roles in Descope (if you use groups, they are converted automatically).
- Import all of your users from the AWS User Pool defined in the environment variables of the script, including custom attributes defined in the user pool.
- Associate all users under specific User Groups with the new roles created in Descope.
Note
If you want to also import multiple user pools into specific Tenants, which is a common ask, you can modify the source code of the tool to handle this. Please refer to our Python SDK documentation on how to implement this.
Configure Local Environment
- Clone the Repo:
- Create a Virtual Environment
- Install the Necessary Python libraries
- Setup Your Environment Variables
a. Descope project ID: Can be found here.
b. Descope Management Key: If you do not already have one stored, you can create one here.
c. Access Key ID and Secret Access Key: Obtain your Access Key ID and Secret Access Key by following the steps outlined on the AWS Guide. Both can be found after an Access key is created.
d. Cognito User Pool ID: This can be found in the AWS Cognito console.
![]()
You can change the name of the .env.example file to .env to use as a template. Then populate with the items generated above:
Running the Migration Script
You can use the -v or --verbose flags to enable more detailed output. This works for both live and dry runs, providing you with additional information.
Dry Run:
The output would appear similar to the following:
Live Run:
The output would appear similar to the below and a log file will be generated in the form migration_log_cognito_%d_%m_%Y_%H:%M:%S.log.
Exporting Users from the Cognito API
If you would like to have complete control over the migration process, you can export users from the Cognito API yourself.
It's recommended to use the AWS SDK for Python (boto3) or other AWS SDKs to list and export users.
Prerequisites
- Access to your AWS account with permissions to read users from Cognito User Pools.
- An IAM user or role with the
cognito-idp:ListUserspermission (orAmazonCognitoPowerUsermanaged policy). - Your Descope project ID and a Descope Management Key.
- The Cognito User Pool ID (found in the Cognito console).
Step 1: Set Up AWS SDK
Install the AWS SDK for Python (boto3):
Configure your AWS credentials. You can either:
- Use the AWS CLI to configure credentials:
aws configure - Set environment variables:
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY - Use IAM roles if running on EC2, Lambda, or ECS
Step 2: Export Users from Cognito
Use the list_users method to retrieve all users from your Cognito User Pool. This method supports pagination, so you'll need to handle the PaginationToken to retrieve all users.
Key details about this API:
- Pagination: The
list_usersAPI returns up to 60 users per page. Use thePaginationTokenfrom the response to retrieve the next page. Continue until noPaginationTokenis returned. - Rate limiting: AWS Cognito enforces request quotas. The
ListUsersAPI has a default limit of 5 requests per second. If you exceed this, you'll receive aTooManyRequestsException. Implement exponential backoff and retry logic. - User attributes: Each user has an
Attributesarray containing objects withNameandValue. Standard attributes includeemail,phone_number,name, etc. Custom attributes are prefixed withcustom:. - User status: The
UserStatusfield indicates the user's state (CONFIRMED,UNCONFIRMED,FORCE_CHANGE_PASSWORD, etc.).
Example response (abbreviated):
Step 3: Export User Groups (Optional)
If you use Cognito User Groups and want to map them to Descope Roles, export the groups and their memberships:
Step 4: Map Cognito Attributes to Descope
For each user, map the exported attributes:
- Cognito
Usernameoremailattribute → DescopeloginIds(required; unique per user). - Cognito
nameattribute → Descopename. - Cognito
given_name→ DescopegivenName. - Cognito
family_name→ DescopefamilyName. - Cognito
email→ Descopeemail. - Cognito
phone_number→ Descopephone. - Cognito
email_verified→ DescopeverifiedEmail. - Cognito
phone_number_verified→ DescopeverifiedPhone. - Custom attributes (
custom:*) → Descope custom attributes (create the corresponding custom attribute definitions in Descope first). - Cognito User Groups → Descope
roleNames(map group names to Descope role names).
Build a JSON array in the format expected by Descope (see user format guide).
Example mapping code:
Step 5: Import Users into Descope
Use the Descope Management API to import users:
- Single user: Create User —
POST /v1/mgmt/user/create - Batch: Batch Create Users —
POST /v1/mgmt/user/create/batch(see rate limits here)
Example batch import code:
When importing, you'll need to make sure all loginId values you import are unique per user.
Optionally set a custom attribute freshlyMigrated to true for post-migration flows (see Post-Migration Verification). You can also set verifiedEmail and verifiedPhone to true if these were already verified in Cognito, so users are not asked to re-verify.
Once the users are imported, you can verify them in the Descope Users list and test sign-in with a few migrated users.
JIT Migration
Note
In order to use JIT migration, you must keep Cognito running until all active users have signed in at least once.
With Just-In-Time (JIT) migration, you do not bulk-export users. Users are provisioned in Descope when they sign in. This approach lets you migrate users gradually without downtime and without needing to coordinate a bulk import.
AWS Cognito does not expose user password hashes, so your JIT architecture depends on whether you want to preserve the password sign-in experience or transition to passwordless. The two main approaches are described below.
Using Cognito USER_PASSWORD_AUTH with a Generic HTTP Connector
Note
This approach requires you to keep your Cognito User Pool and App Client active until all users have migrated. You can decommission Cognito only after all active users have signed in at least once through Descope and been provisioned.
Use this approach if you want to keep passwords working while ensuring that all traffic flows through Descope. Descope collects the username and password, a Generic HTTP Connector calls Cognito's InitiateAuth API with the USER_PASSWORD_AUTH flow to verify the credentials, and on success the flow provisions (or updates) the user in Descope.
1. Configure your Cognito User Pool and App Client
In Cognito, make sure your User Pool and App Client are configured for the username/password flow:
- User pool attributes (User Pool):
UsernameAttributes: includeemail(orphone_number) so users can sign in with that identifier.AutoVerifiedAttributes: includeemailso Cognito automatically sends verification emails.VerificationMessageTemplate/EmailConfiguration: configure how Cognito sends verification emails (for example,DefaultEmailOption: CONFIRM_WITH_LINKandEmailSendingAccount: COGNITO_DEFAULT).
- App client settings (User Pool Client):
SupportedIdentityProviders: includeCOGNITO.ExplicitAuthFlows: include at leastALLOW_USER_PASSWORD_AUTHandALLOW_REFRESH_TOKEN_AUTH.- Remove OAuth-only settings you no longer need (resource server, OAuth scopes, and
GenerateSecretif you are not using client credentials).
With this configuration, Cognito can accept username/password credentials via the USER_PASSWORD_AUTH flow.
2. Configure a Generic HTTP Connector to call Cognito
Create a Generic HTTP Connector in Descope to call the Cognito InitiateAuth API:
- Base URL:
https://cognito-idp.<region>.amazonaws.com(for example,https://cognito-idp.us-east-1.amazonaws.com). - Headers:
Content-Type: application/x-amz-json-1.1X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth
- Method:
POST - Body (template):
You can parameterize ClientId or the region with connector variables or environment-specific configuration if needed.
![]()
3. Build the JIT flow in Descope
In your Descope Flow:
- Check if the user already exists in Descope
- At the start of the flow, use a Condition on
user.loginIds(or an equivalent indicator) to see if the user is already known to Descope. - If
user.loginIdsis not empty (user already exists), you can route them through a normal Sign In / Password or passwordless path in Descope and skip Cognito entirely.
- At the start of the flow, use a Condition on
- Collect credentials for new / unknown users
- If
user.loginIdsis empty (new to Descope), show a Screen that collects email and password (for example,form.emailandform.password). - After the screen, call the Cognito
InitiateAuthendpoint via the Generic HTTP Connector configured above to verify the password against Cognito.
- If
- Branch on Cognito result
- On success (for example, HTTP 200 with an
AuthenticationResult), treat this as either:- An existing Cognito user with a correct password, or
- A brand-new Descope user you are onboarding.
In both cases, use the Sign Up / Password action in Descope to create the user (or idempotently ensure they exist) with the provided email and password, mapping any needed attributes and roles. Subsequent sign-ins can then go directly through Descope without calling Cognito again.
- On failure (Cognito returns an error or non-200), route the user to a Reset Password / Passwordless path:
- For example, send a magic link or OTP, or force a password reset in Descope before allowing access.
- On success (for example, HTTP 200 with an
- Complete the flow
- After successful Sign Up / Password or passwordless recovery, continue the flow as a normal Descope sign-in (issue a Descope session token, set any
freshlyMigratedflags, etc.).
- After successful Sign Up / Password or passwordless recovery, continue the flow as a normal Descope sign-in (issue a Descope session token, set any
Over time, as users sign in via Descope and are provisioned with a Descope password (or passwordless method), you can gradually stop calling Cognito for those users.
Example Cognito API response:
![]()
Using Cognito as a Custom OIDC Provider
Use this approach if you are transitioning to passwordless authentication or are okay with requiring users to reset their password on first sign-in.
You configure your AWS Cognito User Pool as a custom OAuth provider in Descope, so users authenticate through the standard Cognito sign-in experience (including Hosted UI) and are provisioned in Descope automatically on first sign-in.
Note
You'll need to make sure there is an App Client in Cognito that Descope can use to sign in users.
If using an existing Cognito app client, make sure you add the Descope authorized callback URL in the app client's settings.
How it works:
- Configure Cognito in Descope - In Descope, add AWS Cognito as a custom OIDC provider.
- Use your Cognito User Pool domain (e.g.
https://your-domain.auth.us-east-1.amazoncognito.com) - Set the app client ID and secret from your Cognito User Pool
- Configure the OIDC endpoints (Cognito follows standard OIDC discovery at
https://your-domain.auth.region.amazoncognito.com/.well-known/openid-configuration)
- Use your Cognito User Pool domain (e.g.
- Add OIDC sign-in to your flow - In your Descope flow, add a Sign Up or In / OAuth step that redirects users to AWS Cognito. They will then sign in through Cognito as usual (including MFA, if enabled).
- User is provisioned in Descope - After Cognito authenticates the user, it returns an ID token with claims (email, name, custom attributes). Descope provisions the user and maps those claims to Descope user attributes, then issues a Descope session token.
Once a user has been provisioned in Descope, subsequent sign-ins can go directly through Descope, controlled via a condition in your flow.
![]()
SSO Migration
If your AWS Cognito setup includes SSO (SAML/OIDC) for organizations or partners, you can migrate those configurations to Descope without forcing admins to re-configure their IdPs.
Descope can consume the existing IdP response and complete authentication so end users keep a seamless experience. For the full process—implementing SSO with Descope, setting up tenants, DNS redirect, and testing—see SSO Migration.
AWS Service Integration
If you use other AWS services that rely on Cognito tokens (API Gateway, AppSync, Lambda authorizers), you'll need to configure them to accept Descope tokens after migration.
Transitioning to AWS JWT Authorizers
This is necessary if you want to continue to use Descope with API Gateway. You can configure AWS JWT Authorizers as detailed in AWS JWT Authorizer with Descope KB article.
Using AppSync Endpoints with Descope
This is necessary if you want to continue to use Descope with GraphQL endpoints and AppSync. A detailed guide on how to implement this can be found in our AppSync Authorizer KB article.
Session Validation Strategy
Whether you do a full migration or a JIT migration, plan for a period where both your backend and any API Gateway layer (for example, AWS API Gateway with a Cognito authorizer) may need to accept Descope and Cognito tokens at the same time.
Your backend (and, if applicable, your API Gateway authorizer) should support both token types during the transition:
- Validate Descope session tokens (JWTs) for users who have already migrated or signed in via Descope.
- Validate AWS Cognito tokens for users who still have active Cognito sessions.
Inspect the token (e.g. issuer or kid in the JWT header) to determine the provider, then validate accordingly. This lets you roll out the migration gradually.
For the implementation pattern, see the docs on backend session validation here.
Example dual validation logic (Python):