AWS Cognito (OIDC)
With the power of OpenID Connect, Descope acts as a federated identity provider to handle user authentication while Amazon Cognito acts as the primary identity provider and the user identity information store.
With this integration, new users are automatically created in the user pool. An AWS Lambda function will then trigger to merge user identities in Amazon Cognito and retain all necessary roles and permissions.
The simplified flow diagram below shows this process:
Follow the steps in this guide to configure your Amazon Cognito app to use Descope Flows.
Setting up your Descope Flow
Note
If you want to use Passkeys, you can download the oidc-flow JSON from our sample app repository, which you can import into your own project.
It is important to use this Flow, as it is designed to make sure the user and their email is always verified when using passkeys as an authentication method for security reasons.
Your flows are automatically hosted with our Descope Auth Hosting Application. To learn more about our hosted app, you can read about it in our Docs page here.
If you're using the oidc-flow.json
provided above, edit the query parameter at the end of the Flow Hosting URL like so:
https://auth.descope.io/<Project ID>?flow=oidc-flow
Note
You should keep this page open, as you're going to need this information for the next parts of this guide.
If you would like to edit the UI of the login screen, you can do that in the Flow Editor. Once your flow is complete and your login redirect has been configured, you'll need to connect your Flow to Amazon Cognito by setting Descope up as an external provider.
Descope as an external provider
AWS Cloud Formation Script
If you wish to use a pre-built Cloud Formation script to setup Descope as an external provider, rather than following the steps individually, you can use the following script:
This script will perform the following actions:
- Conditional User Pool Creation - The script can either use an existing Cognito User Pool or create a new one based on the input provided.
- Descope Integration - It sets up Descope as an external identity provider, configuring it with necessary credentials and settings.
- Identity Federation Handling Through the Lambda function, it handles scenarios where a user may have identities in both Descope and Cognito, merging these to maintain a consistent user identity across different login methods.
- IAM Role Configuration for Lambda - Ensures that the Lambda function has the necessary permissions to execute its intended operations, particularly those involving interactions with the Cognito User Pool.
Note
Unless you're intimately familiar with Cloud Formation and understand how to use these scripts, it's recommended to perform the steps listed below manually one-by-one. That way you can control the configuration in a more granular fashion.
Copy this lambda, and change the following values to use it:
- Parameters - You will need to specify the following items:
- Descope Project ID - Your Project ID from Project Settings
- Descope Access Key - An Access Key secret generated under Access Keys
- Cognito User Pool ID (Optional) - If you want to apply this configuration to a previously defined Cognito user pool, include the ID in your cloud formation script
- Issuer URL from Descope - The issuer URL of Descope (as an OIDC provider). This can be found under Applications -> Your OIDC app -> Issuer
- User Pool and Client Properties - You need to specify the properties for the Cognito User Pool and User Pool Client according to your requirements.
- Lambda Function Code - The Lambda function code should be written in Python and embedded in the
ZipFile
property under theOIDCUserMergeLambda
resource. Alternatively, you can upload the code to an S3 bucket and reference it in the template.
Important Notes:
- IAM Role and Policies - The
LambdaExecutionRole
is configured to allow the Lambda function to perform actions on Cognito User Pools. Adjust the permissions according to your security requirements. - Testing and Security - Test this template in a controlled environment before deploying it in production. Ensure that all sensitive information is handled securely.
Once you've run this code, Descope should be configured as an external provider, and you should be good to go! If you're interested in viewing a sample application of Cognito working with Descope, you can skip to the bottom of this doc
Creating / Using a User Pool
In order to set up Descope as an external provider with Amazon Cognito, you'll first need to create a user pool in Amazon Cognito, if you don't already have one. Just make sure that your user pool requires the email attribute and allows a sign in with email option.
Once you have a configured user pool, you'll need to set up Descope as an external provider.
What you'll need from the Descope Console
In the Descope Console, you'll need a few things in order to configure the external provider. Fetch all of this information and put it in the respective fields in the configuration page:
- Provider Name: Call it Descope
- Issuer URL: This is the Issuer URL which is found under your Authentication Methods -> SSO -> Identity Provider configuration settings
- Client ID: Your Descope Project ID, which can be found under Project Settings in the Descope Console
- Client Secret: Access key generated under Access Keys in the Descope Console
Creating the External Provider in Cognito
-
Under Authorized Scopes, add the following scopes: openid profile email descope.custom_claims descope.claims. The descope.custom_claims scope will allow us to include custom claims defined in the flow, and the descope.claims scope will return roles / permissions, tenants in the JWT to return back to Cognito.
-
Make sure that the Attribute Request Method is GET. There's no need to add an Identifier.
Note
If custom claims or roles are not passed in from Descope, but if they are configured to be accepted in your Cognito configuration, then the previous value will be saved in the Cognito user and not be replaced. As an example, if you're mapping roles but then the use in Descope originally has a role and then all role information is removed. In that case, the original role information will persist in Cognito. However, new values such as an updated Role name will replace the original value.
At this point, your configuration screen should look something like this:
-
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 (e.g.
https://api.descope.com/<Project ID>
) -
Finally, you'll need to map your attributes between Descope and Amazon Cognito in the following fashion:
Here, you can edit the custom claims that come back to Amazon Cognito after Descope authentication is complete if you wish. You will need to map the corresponding key in the Custom Claim action (in the Flow) with the attributes mapped here.
If you're using the Cognito Hosted UI, you should now automatically see an option to sign in with Descope:
However, if you're using a custom UI (as I suspect most of you are), then you'll need to add a way to start the OIDC flow. To start the OIDC flow, you'll need to navigate to the OAuth /authorize
endpoint, as explained here. Here is an example URL:
Once you have your external provider configured and your login screen working, you should be able to sign in with Descope, based on how you've configured your Flow. All of the OIDC logic will work in the background.
Your users should be able to sign in using your personalized Descope Flow or continue using the traditional authentication methods defined by your user pool.
So we're done right? Well, almost…
Setting up an AWS Lambda trigger
Since we're using Descope as a federated IdP, the users will not automatically merge together. This means that if a user logs in with Descope and logs in with Amazon Cognito, two separate users with differing permissions and roles will be configured in the user pool. We can handle this issue by using an AWS Lambda trigger to merge the user identities, so that the same user can use either method of login and gain access to the same account.
To configure this user merge functionality follow the steps below:
-
Head to the User Pool Properties tab in your Amazon Cognito dashboard, and under Lambda Triggers, select Add Lambda Trigger. This will open up a new tab.
-
Configure your Lambda trigger to look like the screenshot below, and then select Create a Lambda Trigger:
-
Select Create Function in the top right corner, and configure the following items:
- Function name: OIDC_USER_MERGE
- Runtime: Select Python
-
Create the function, and then in the Code section, paste the code snippet shown below. This function will also print out the needed event and user information when merging identities, so that you can track it in your AWS CloudWatch logs.
- Finally, go back to the original tab you had open, make sure that the Lambda trigger is selected, and add it to your user pool with the Add Lambda trigger button:
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
In order to use this Lambda trigger to merge identities, the SDK being used will need to have access to your user pool and all of the identities stored in it. If you try logging in with Descope at this time, you'll notice that the user merging fails because of a permission issue.
To resolve this, you'll need 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.
You can do that by following the steps below:
- Head to your 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.
- Give permission to all Cognito User Pool actions, and make sure you specify what Resource ARNs you will need for the user merging process. In the example below, I've selected all, but you will most likely only need to access to the userpool ARN.
- After clicking Next, your final screen should look something like the screenshot below. Add a name and description to the policy and click on Create Policy.
- Now head to the Roles section of the IAM Console, search for the OIDC_PROD_MERGE (or whatever you decided to name the Lambda trigger you created in the previous section) and select it.
- Select Attach policies under Add permissions.
- Search for the policy you created, and add it as a permission policy to this specific role. After that, your Lambda trigger will have full access to all of the user identity information and will be able to use the Amazon Cognito SDK to perform the merging tasks.
Now, whenever you sign in with Descope, it will automatically check to see if the user identity already exists in the user pool based on the user's email coming from Descope. If a user already exists, all relevant properties will be merged.
Note
If you are using AWS Lambda triggers to merge the user identity, make sure in your Flow that the user has been verified by OTP, Magic Link, or by signing in with OAuth Providers like Google or Facebook. The reason for this, is to ensure that the Descope user account you're merging with the one in Cognito is indeed associate with that same user. In the oidc-flow.json
Flow provided for Descope Passkeys above, for example, the user's email is verified before the flow is completed.
Sample App
If you're interested in seeing how this is implemented in a sample React application, feel free to check out our sample app on GitHub.
If you have any other questions about Descope or our flows, feel free to reach out to us!