Using Cognito User Pool as an OpenID Connect Provider
Tags: aws, cognito, oidc, openid
Published: 2020-04-14 10:56:49

Couple of weeks ago I was involved in a hackathon which involved integrating two Cognito user pools together. During my investigation I discovered that Cognito user pool supports OAuth2 for user authentication. This post will outline how you can use Cognito user pool as an OIDC provider as well as how you can connect two user pools together.

# Who is this post for?
This post is for you if you:
- Have basic understanding of AWS Cognito user pools
- Want to know how Cognito user pools can be used as a OIDC provider
- How to associate two user pools together through federated identity providers

# Sections
- [What is OpenID Connect?] (#chapter-1)
- [User Pool Setup] (#chapter-2)
- [Configuring Federated Identity Provider] (#chapter-3)
- [Testing] (#chapter-4)

# What is OpenID Connect? <a name="chapter-1"></a>
OpenID is a protocol which provides a mechanism to authenticate users through a third party. This means that users can login to your application with existing accounts on other platforms. When a user completes the OIDC flow, your application can gain access to some of their information from the third party in the form of an `id_token` which is a JWT token.


# User Pool Setup <a name="chapter-2"></a>
In this section we'll create two Cognito user pools and configure them so they can integrate together. `oidc-pool-1` will be the master user pool which uses `oidc-pool-2` as a federated identity provider. We'll create a user in `oidc-pool-2` and keep `oidc-pool-1` empty.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/w85z4lq7prb7emt9h19c.png)

Search for *Cognito* on the AWS console and click on *Manage User Pools*. Click *Create a user pool* on the top right corner.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/er9f003c7ccppotybzqx.png)

After entering the name of your user pool, you are presented with two options. I'll be clicking *Review defaults* for the purpose of this demo but you're welcome to step through the settings. This option will let you specify some crucial settings for your user pool such as required attributes for a user. Note that this information **cannot** be changed once you create your user pool.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ydkqncz9hm3uporlgb54.png)
Once you've created a user pool, navigate to *App clients*.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xapwoist3au23oo48389.png)
An app client has access to invoke certain endpoints on your user pool while being unauthenticated. Decide on a name and create the app client. We'll leave rest of the settings as is.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/759oj6dqfgv3w1s4tw0f.png)
Head over to the *Domain name* page and register a domain for your user pool. Take note of the domain that you enter here.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vt6j2eeufictvrh4fztb.png)
Repeat the process above and create another user pool. Apply the exact same configuration as the first user pool. Take note of the *App client id* and *App client secret* as well as the *Pool Id* for the second user pool, we will need this when we integrate the two user pools together.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vt6j2eeufictvrh4fztb.png)
If you followed the post up to this point, you'll now have two Cognito user pools, each with an app client and a domain. If you're satisfied with your setup, navigate to *App client settings* of your **second** user pool.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/j7vnydmzcxfs5uma3tif.png)
This is where the integration begins. Choose *Select all* for *Enabled Identity Providers* and set the *Callback URL(s)* in the following format:

```
https://${USER_POOL_DOMAIN}/oauth2/idpresponse
``` 

This allows users authenticated with the second user pool to be redirected to the master user pool. 

Since I didn't prepare any web app to demo this integration, I'm setting a random value on the *Sign out URL(s)*. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6n6448tyojog4yx71le8.png)
Select *Authorization code grant* and *openid* under *OAuth 2.0* and click *Save changes*.


# Configuring Federated Identity Provider <a name="chapter-3"></a>
Cognito user pools support integration with federated identity providers such as Google and Facebook. We'll use this feature to integrate our user pools together.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/fg3g8astt3xtczuiis7o.png)
Navigate to *Identity providers* on the **first** user pool. Select *OpenID Connect* and you'll see the form below.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8zdmuugigoaz2io8qoq9.png)

Here's how you should fill in the form:
```
Provider name: Arbitrary name
Client ID: App client id of the second user pool
Client secret: App client secret of the second user pool
Attributes request method: GET
Authorize scope: openid
Issuer: https://cognito-idp.${REGION}.amazonaws.com/${SECOND_USER_POOL_ID}
```
User pool ID can be found on the *General settings* menu. Click on *Run discovery* once you've filled in the form.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ebsh070nhacgvoijelbv.png)
Don't be worried if you see this warning.

Open a new tab in your browser and navigate to https://cognito-idp.${REGION}.amazonaws.com/${SECOND_USER_POOL_ID}/.well-known/openid-configuration. You'll see a JSON response with a set of properties. This information can be used to fill in the rest of the form.

```
Authorization endpoint: https://${SECOND_USER_POOL_DOMAIN}/oauth2/authorize
Token endpoint: https://${SECOND_USER_POOL_DOMAIN}/oauth2/token
Userinfo endpoint: https://${SECOND_USER_POOL_DOMAIN}/oauth2/userInfo
Jwks uri: https://cognito-idp.${REGION}.amazonaws.com/${SECOND_USER_POOL_ID}/.well-known/jwks.json
```

Click on *Create provider* Once you've filled in the form completely.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vo5xqqqskdkyzjxqyl4t.png)
Navigate to *App client settings* on your **master** user pool. Select the name of the federated identity provider that you just added (and *Cognito User Pool* if you want to support direct login through this client). Add suitable URLs for callback and sign out. Check *Authorization code grant* and *openid* in OAuth 2.0 and click on *Save changes*. There is only one step left to integrate the two user pools.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/na658kn0d1l2tni7kfcg.png)
Navigate to *Attribute mapping* page of the **master** user pool. Click on the *OIDC* tab. This form indicates how the attributes of the **second** user pool maps to the attributes of the **master** user pool. 

When you land on this page for the first time, you'll only see a `sub` attribute. This is a built-in mapping which acts as a unique identifier of the user. If you're using the default user pool configurations you can add the email related mappings as can be seen above. Otherwise you'll want to map whatever attribute makes sense for your use case. Note that you **must** add attributes that are marked as *required* on your **master** user pool. Click on *Save changes* once you're happy with the attribute mapping.

This marks the end of this integration! Continue to the next section if you want to find out how you can test it using Cognito Hosted UI.

# Testing <a name="chapter-4"></a>
To simply the testing process, we'll be using Cognito Hosted UI. This is a built-in set of UI elements that allows users to perform basic auth flows such as sign-in, sign-up and sign-out. In reality, you might be using a custom web app with a library such as <a href=https://aws.amazon.com/amplify/>AWS Amplify</a> to simplify the authentication process.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4xwzpah5vgbo6onb9h00.png)
Navigate to *Users and groups* on the **second** user pool and create a user. For the sake of this post I'll have *Mark email as verified* ticked. If you're using the same user pool configuration as I am, your user will be in *FORCE_CHANGE_PASSWORD* state.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2a4u23zuekvvsh07xbnt.png)
Navigate to *App client settings* on your **master** user pool and click on *Launch Hosted UI*.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/745fgx1u6i2m0dqpeiim.png)
If you selected both *YOUR_OIDC_PROVIDER_NAME* and *Cognito User Pool on your *App client settings*, you'll see the same page as I do. Click on the button below *Sign in with your corporate ID*.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/43djvtxc615r5dbapyj8.png)
From this page you can sign in as the user you've created on your **second** user pool. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/qfgvxfhk8zsd8l9nj0hg.png)
Depending on your user pool configurations, you might see this page when you sign in. Simply enter a new password for your user. After this step you may notice that your user in the **second** user pool is now in a *CONFIRMED* state. Try logging in again on the Hosted UI.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ks7hkkbln4dxu14nlbfy.png)
If you're redirected to google.com (or whatever your redirect URL on the **master** user pool is) with the `code` in the query parameter, you've set everything up correctly and the integration is working as expected! Head over to *Users and groups* on your **master** user pool to confirm that a new user has been created (it was previously empty). This user should have correct attributes pulled from the **second** user pool.

# Conclusion
We've seen how Cognito user pool can be used as an OIDC provider as well as how we can use this concept to associate two user pools together. If you want to integrate your user pool with another service, you can use the same endpoints provided on this post.

Lastly if you want to integrate this flow with a web app, I again recommend looking into <a href=https://aws-amplify.github.io/docs/js/authentication>AWS Amplify and how it handles authentication</a>.