Skip to main contentArrow Right

Table of Contents

Amazon Cognito is a widely used authentication and user management service that enables developers to add user sign-up, sign-in, and access control to web and mobile applications. However, relying on password-based authentication in Amazon Cognito can lead to user friction, especially when passwords are forgotten, not to mention the increased risk of credential stuffing and account takeovers.

To reduce these risks and improve user experience, Descope now supports OIDC federated authentication. This makes it easy to add passkeys to Amazon Cognito user pools, enabling secure, passwordless login without requiring changes to your app’s core code.

In this guide, you’ll learn how to enable Amazon Cognito passkeys with Descope, allowing your users to authenticate more securely and conveniently — no passwords required.

Benefits of adding passkeys to AWS Cognito

Here’s what this integration enables when you add passkeys to Amazon Cognito using Descope:

  • Simplified user management: Descope acts as an OIDC provider, allowing users to authenticate with passkeys while Amazon Cognito manages the user pool and device syncing. This offloads identity complexity from your team and speeds up development.

  • Enhanced security: By introducing Amazon Cognito passkeys, you eliminate passwords from the authentication flow. This reduces the risk of breaches, account takeovers, and other password-based attacks.

  • Improved user experience: Passkeys offer a fast, seamless login experience across devices. No more forgotten passwords — just tap and go. This makes users more likely to return and stay engaged.

Read more: Passkeys vs Passwords: What’s the Difference? 

How adding passkeys to AWS Cognito with Descope works

Using OpenID Connect (OIDC), Descope serves as a federated identity provider that handles passkey-based user authentication. Amazon Cognito continues to act as the primary identity provider and stores user identity data.

When you add passkeys to Amazon Cognito through Descope, new users are automatically added to the user pool upon registration. For existing users who want to switch from password-based sign-in to passkeys, an AWS Lambda function is used to merge user identities while preserving roles and permissions.

The flow diagram below illustrates this process:

Cognito OIDC Diagram
Fig: Descope as federated IdP with Amazon Cognito

From a developer’s perspective, the integration is lightweight and requires minimal ongoing configuration. This lets your team focus more on product innovation instead of user authentication logic.

Now that we’ve covered the why and how, let’s walk through the steps to set this up in your own user pool.

Setting up your Descope Flow

Descope Flows are drag & drop workflows that let you implement authentication with minimal code. In this section, we’ll walk through how to build a Flow that enables secure Amazon Cognito passkeys authentication.

To get started, download the oidc-flow JSON file from our sample app repository and import it into your Descope project. This specific Flow ensures that the user and their email are always verified — a critical step when using passkeys for authentication.

adding flows
Fig: Descope Flows page where you import the JSON file

You’ll also need to embed the Flow component in your application using either React or an HTML Web Component. In the Descope Console, specify where the component is hosted. This is important because Amazon Cognito will need to know where to redirect users when they choose Login with Passkeys on your main login screen.

descope console oidc configuration
Fig: Flow Hosting URL is the URL of where your Flow is embedded

Note: Keep this page open — you’ll need these details in the upcoming steps.

If you want to customize the UI of the passkey login screen, you can do so in the Flow Editor. Once your Flow is complete and your redirect URL is configured, you’re ready to connect Descope to Amazon Cognito as an external identity provider.

Descope as an external OIDC provider

To add passkeys to Amazon Cognito using Descope, you’ll need to set up Descope as an external identity provider via OIDC.

Start by creating a user pool in Amazon Cognito if you haven’t already. Most readers likely have one configured, but if not, make sure the user pool requires the email attribute:

required attributes - email
Fig: Make sure your user pool has email as a required attribute

And that sign-in with email is enabled:

user pool sign in options - email
Fig: Make sure your user pool allows users to sign in with email

Once your user pool is ready, follow these steps to configure Descope as an external OIDC provider:

First, in the Descope Console, go to Authentication MethodsSSO OpenID Connect. Collect and input the following values:

  • Provider Name: Descope

  • Client ID: Your Descope Project ID (found under Project)

  • Client Secret: API key generated under Access Keys

  • Issuer URL: Found on the Authentication Methods screen, this includes all OIDC standard metadata for the authentication process. If you’re using a CNAME, use your custom domain instead of api.descope.com. Example: https://example.company.com/<Project ID>

Second, under Authorized Scopes, add: openid profile email descope:custom_claims. The descope:custom_claims scope enables roles, tenants, and other custom attributes to be returned in the JWT and passed to Cognito.

Third, set the Attribute Request Method to GET. You don’t need to add an Identifier. At this point, your configuration screen should look something like this:

oidc configuration
Fig: Setting up OIDC federation in Cognito

Fourth, under Retrieve OIDC Endpoints, select the Auto fill through Issuer URL option and paste in the Issuer URL you got from the Console in step 2:

issuer url
Fig: Where the issuer URL is located

Finally, map the attributes between Descope and Amazon Cognito in the following fashion:

oidc configuration attribute mapping
Fig: Mapping attributes between Cognito and Descope

You can modify the custom claims returned after authentication if needed. Make sure the keys in your Flow’s Custom Claim action match those mapped here.

Custom Claims Action
Fig: Custom Claims action in a Descope Flow

If you’re using the Cognito Hosted UI, should now see a Sign in with Descope option automatically:

cognito hosted ui
Fig: Cognito Hosted UI with Descope login

However, if you’re using a custom UI as most apps do, then you’ll need to add a Descope login button to redirect users to the embedded Flow component. To do this, make sure that your button hits the Cognito OAuth /authorize endpoint to trigger the OIDC login flow.

Here’s the basic structure: https://<user pool name>.auth.<aws region id>.amazoncognito.com/oauth2/authorize?

With the query parameters:

  • identity_provider=Descope (OIDC Provider Name set in Cognito)

  • &redirect_uri=<Redirect URI (when login is successful)>

  • &response_type=CODE

  • &client_id=<Cognito App Client ID>

  • &scope=email openid phone

You can learn more about how to configure the OAuth /authorize endpoint in the Amazon Cognito Documentation.

Once this is configured, your users can log in with passkeys via Descope and all OIDC logic will run behind the scenes. Users can also continue logging in with any traditional methods still supported in your user pool.

So we’re done right? Well, almost…

Setting up an AWS Lambda trigger

When you add passkeys to Amazon Cognito using Descope, you’re introducing a federated identity provider into your authentication flow. However, this means that Amazon Cognito may treat Descope-authenticated users as entirely new accounts even if their email matches an existing user.

To prevent duplicate users and ensure role/permission continuity, you’ll need to set up an AWS Lambda trigger that merges identities based on email.

Follow these steps to configure the user merge:

  • Go to the User Pool Properties tab in your Amazon Cognito dashboard. Under Lambda Triggers, select Add Lambda Trigger (this opens in a new tab).

  • Configure your Lambda trigger to look like the screenshot below, and then select Create a Lambda Trigger:

create sign up lamdba trigger
Fig: Create a Lambda trigger
  • Click Create Function in the top right corner, and use the following settings:

    • Function name: OIDC_USER_MERGE

    • Runtime: Select Python

  • After creating the function, scroll to the Code section and paste the snippet below. This function looks for users with the same email and links them together, allowing seamless login via Descope or Cognito. It also logs useful event data to CloudWatch:

import boto3

client = boto3.client('cognito-idp')

def lambda_handler(event, context):
    print("Event: ", event)
    email = event['request']['userAttributes']['email']

    # Find a user with the same email
    response = client.list_users(
        UserPoolId=event['userPoolId'],
        AttributesToGet=[
            'email',
        ],
        Filter='email = "{}"'.format(email)
    )

    print('Users found: ', response['Users'])

    for user in response['Users']:
        provider = None
        provider_value = None
        # Check which provider it is using
        if event['userName'].startswith('descope_'):
            provider = 'Descope'
            
            provider_value = event['request']['userAttributes']['name']

        print('Linking accounts from Email {} with provider {} provider_value {} '.format(
            email,
            provider,
            provider_value
        ))

        # If the signup is coming from a social provider, link the accounts
        # with admin_link_provider_for_user function
        if provider and provider_value:
            print('> Linking user: ', user)
            print('> Provider Id: ', provider_value)
            response = client.admin_link_provider_for_user(
                UserPoolId=event['userPoolId'],
                DestinationUser={
                    'ProviderName': 'Cognito',
                    'ProviderAttributeValue': user['Username']
                },
                SourceUser={
                    'ProviderName': provider,
                    'ProviderAttributeName': 'Cognito_Subject',
                    'ProviderAttributeValue': provider_value
                }
            )
    
    # Return the event to continue the workflow
    return event
  • Finally, go back to the original tab where you began the Lambda setup, confirm the new trigger is selected, and click Add Lambda Trigger to apply it to your user pool:

create lamdba trigger
Fig: Add Lambda trigger to user pool

We’re almost there! Now all we have to do is make sure that the Lambda trigger has the correct permissions configured to be able to access the user identities through the SDK.

Adding permissions for Lambda trigger

To successfully merge users when you add passkeys to Amazon Cognito, your Lambda function needs the proper IAM permissions to access and update user identities.

Without these permissions, merging users during attempts to log in with Descope will fail due to permission issue.

To resolve this, you have to create a new identity permissions policy in the AWS IAM Console, and make sure that Lambda trigger role is assigned to that new policy.

Follow these steps:

  • Go to the IAM Console, select Policies, and click on the blue Create New Policy button in the top right hand corner.

  • Under Select a Service, select Cognito User Pools.

  • Grant permission to all necessary Cognito actions. In most cases, you only need access to your specific userpool ARN. For testing or broad access, you may initially allow access to all.

iam policy permissions configurations
Fig: Defining permissions in AWS IAM
  • Click Next, name your policy, add a description, and click Create Policy.

creating iam policy second step
Fig: Creating an AWS IAM policy
  • Now head to the Roles section of the IAM Console, and search for the role tied to your Lambda trigger (e.g. the OIDC_PROD_MERGE) and select it.

roles list in iam console
Fig: Roles list in AWS IAM
  • Click Attach policies, find the policy you just created, and add it to the role.

attach policy to oidc prod merge role
Fig: Add permissions to your role

Once attached, your Lambda trigger will have full access to user identity data via the Cognito SDK, allowing it to link Descope users with their Cognito counterparts during sign-in.

Descope will automatically check whether a user already exists in your Amazon Cognito user pool, using their verified email tied to biometrics. If a match is found, user identities are merged seamlessly, preserving roles, permissions, and settings.

There’s no added security risk here. Since Descope enforces email verification whenever passkeys or biometrics are used, a malicious user can’t spoof access with someone else’s email. The Flow simply won’t complete unless verification succeeds.

With this integration, you can add passkeys to Amazon Cognito and deliver a fast, frictionless, and secure login experience — no passwords, no compromises.

If you’re interested in seeing how this is implemented in a sample React application, check out our sample app on GitHub. 

Got questions or stuck on a step? Join us in AuthTown, our open developer community where we talk authentication, share tips, and help each other build better login experiences.

Descope: beyond Amazon Cognito passkeys

Descope is a drag & drop CIAM platform that helps developers ship secure login flows with ease. From passkeys and magic links to social login, MFA, and session management — Descope offers a full suite of passwordless options designed to reduce user friction and improve security.

If you are an Amazon Cognito customer and want to explore more about authentication in AWS SaaS apps, sign up for a Free Forever account or book a demo with our experts.