AWS Security Blog
Building an App Using Amazon Cognito and an OpenID Connect Identity Provider
January 11, 2023: This blog post has been updated to reflect the correct OAuth 2.0 endpoint for the Identity Provider (IdP) used and to use an updated version of the AWS SDK for JavaScript. This post has also been refreshed with updated steps to configure an Amazon Cognito Identity Pool and creating a Connected App within Salesforce.
Today, I’m happy to announce that AWS now supports OpenID Connect (OIDC), an open standard that enables app developers to leverage additional identity providers for authentication. Now you can use Amazon Cognito to easily build AWS-powered apps that use identities from any provider that supports this industry standard. This compliments the existing capabilities to use identities from providers such as Login with Amazon, Facebook, and Google.
In this blog post, I will show you how I used Cognito to build a sample AWS-powered app that uses an OIDC identity provider. The JavaScript app allows users to sign in using their Salesforce user names and passwords and enables them to access data stored in an Amazon DynamoDB table. At the end, I will show a fully functional sample app that you can later customize to meet your needs. I used Salesforce as the identity provider, but similar steps can be applied for any other provider that supports OIDC. Moreover, similar steps can be applied for accessing any other AWS service.
The following diagram depicts the overall flow when the Salesforce user accesses the sample app.
- The user accesses the sample app and clicks on Sign In with Salesforce button.
- The app redirects the user to Salesforce for signing in. After successful authentication, the app receives an ID token from Salesforce.
- The app exchanges the ID token for a Cognito token.
- The app exchanges the Cognito token for temporary AWS security credentials.
- The app uses the credentials to access a DynamoDB table.
It is worthwhile to note that the app never stores any long-term credentials and that the AWS SDK for JavaScript helps you accomplish steps 3 to 5 with just a few lines of code.
Prerequisites
To follow along, you’ll need the following:
- A Salesforce account. I used the free Developer Edition.
- An AWS account. If you don’t have one, you can get started with AWS for free.
- A DynamoDB table with few items. If you don’t have one, you can create ProductCatalog example table and add items.
- A web server to host the sample app. I used my laptop running Internet Information Services (IIS) 7 on Windows 7.
Overview
Here is the list of tasks:
- Create a connected app in Salesforce
- Configure the AWS account
- Configure the web server
- Create the sample app
- Test the sample app
Task 1 – Create a connected app in Salesforce
The first task is to create a connected app in Salesforce. This registers the sample app with Salesforce and provides a consumer key (sometimes called an “audience” or a “client ID”) that the app needs in order to successfully redirect the users to Salesforce.
- Log in to Salesforce at https://login.salesforce.com.
- Click Setup on the top-right corner.
- From the left-hand navigation pane, in the Platform Tools section, expand Apps, and click App Manager.
- In the upper right corner click New Connected App
- Scroll to the bottom until you see the Connected Apps section and click New.
- For Connected App Name, specify a name for the app e.g. Cognito OIDC Sample
- For Contact Email, specify a contact email address.
- Under API (Enable OAuth Settings), check the Enable OAuth Settings checkbox.
- For Callback URL, specify the path to callback.html file on your webserver (more on this later). Since I am running the webserver locally on my laptop, I specified https://localhost/callback.html
- For Selected OAuth Scopes, select Access unique user identifiers (openid) and click the Add button. When done, the page looks something like this:
- Click Save at the bottom of the page. Salesforce displays a summary of the new connected app.
- Make note of the Consumer Key that is listed, because we will need it later when creating the sample app.
The final step in Task 1 is to obtain your Salesforce My Domain URL. This URL is what will be used within the ID Token from Salesforce and will needed later when creating the sample app.
- While still logged into Salesforce, click Setup on the top-right corner.
- From the left-hand navigation pane, in the Settings section, expand Company Settings, and click My Domain.
- Make note of the Current My Domain URL that is listed, because we will need it later when creating the sample app.
Task 2 – Configure the AWS account
The next task is to configure the AWS account so that it’s ready for the sample app. The first step is to create a new OIDC identity provider in Identity and Access Management (IAM) which holds information about Salesforce and the connected app created in Task 1. This provides a way for the AWS account to identity users from the OIDC identity provider.
- Sign in to the IAM Console.
- In the navigation pane select Identity Providers and then click Create Provider.
- For Provider Type, select OpenID Connect.
- For Provider URL, specify https://login.salesforce.com and click Get thumbprint
- For Audience, specify the consumer key obtained in Task 1 and click Add Provider.
- From the Identity providers list, click on the name of the provider just created (login.salesforce.com) to open the provider details page.
- Make note of the Provider ARN (Amazon Resource Name) because we will need it later when creating the sample app.
The second step creates an identity pool in Cognito. This step will create an IAM role that the sample app will assume in order to get temporary AWS security credentials that can be used to access the DynamoDB table.
- Sign in to the Cognito Console.
- Click Manage Identity Pools or New Identity Pool if an identity pool already exists.
- For Identity Pool Name, specify a name for the pool e.g. Salesforce
- Expand the Authentication providers section and under OpenID Connect Providers, select the provider created in the previous step (login.salesforce.com) and click Create Pool.
- On the next page, expand the View Details section and expand the View Policy Document section for the first Role Summary (Cognito_SalesforceAuth_Role).
- Within the Policy Document for the Cognito_SalesforceAuth_Role, click Edit.
- Copy-paste the following policy after replacing the resource ARN with the ARN of your DynamoDB table. This policy grants only the scan permission for that particular DynamoDB table.
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": "dynamodb:scan", "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/ProductCatalog" }] }
- At the bottom of the page, click Allow.
- On the next page, in the top-right corner, click Edit Identity Pool.
- Make note of the Identity Pool ID because we will need it later when creating the sample app.
Task 3 – Configure the web server
As part of the authentication flow, Salesforce redirects the user to a SSL (Secure Sockets Layer) enabled URL (i.e., HTTPS). Therefore, the sample app needs a publicly accessible web site and a certificate. For this example, I used IIS 7 running under Windows 7and a self-signed certificate. You can also use Apache running under Linux or Unix. If you don’t have direct access to a computer that can host the website, you can use Amazon EC2 to launch an instance that includes a web server. (If you use EC2, charges might apply.)
- On the computer that has IIS installed, create a self-signed certificate.
- Create a new folder named c:oidc.
- Start IIS Manager by navigating to Start, All Programs, Administrative Tools, and Internet Information Services (IIS) Manager.
- In the left pane, right-click your computer name and select Add Web Site….
- For Site name, enter Cognito OIDC Sample
- For Physical path, enter c:oidc.
- Under Binding, for Type specify https and for SSL certificate choose the self-signed certificate created in Step 1. Once complete, the dialog should look like this.
- Click OK.
Task 4 – Create the sample app
The sample app contains only two files – index.html and callback.html. In this section I’ll show the complete code listings for both the files. To follow along, you’ll need to update the files to match your Salesforce and AWS settings and then copy them to the c:oidc folder you created in Task 3.
index.html
The index.html file displays the Sign In with Salesforce button and redirects the user to Salesforce for authentication. This file is the starting point of the sample app. It requires the consumer key (or client ID) recorded in Task 1, which is how Salesforce knows which app the sign-in request is coming from.
The following listing shows the complete markup and code for index.html.
Note: Replace client_id in the JavaScript code with the consumer key recorded in Task 1.
<!DOCTYPE html> <html> <head> <title>Cognito OIDC Sample</title> </head> <body> <h2>Sample App - Amazon Cognito and OpenID Connect</h2> <p> This sample demonstrates how you can use <a href="http://thinkwithwp.com/cognito/" target="_blank">Amazon Cognito</a> to build a web app that allows users to sign in using their Salesforce user names and passwords and enables them to access data stored in an <a href="http://thinkwithwp.com/dynamodb/" target="_blank"> Amazon DynamoDB</a> table. The app is written entirely in JavaScript and contains only two files - <b>index.html</b> (this file) and <b>callback.html</b>. </p> <p> For more details including a step-by-step tutorial, check our OpenID Connect announcement <a href="http://blogs.thinkwithwp.com/security/post/Tx3LP54JOGBE0AY/Building-an-App-using-Amazon-Cognito-and-an-OpenID-Connect-Identity-Provider" target="_blank">blog post</a>. </p> <p> To get started, click <b>Sign In with Salesforce</b> button. If you authenticate successfully, the app will list the items from a DynamoDB table. </p> <button onclick="login()">Sign In with Salesforce</button> </body> <script type="text/javascript"> function login() { // Consumer key for the connected app created in Salesforce // **** REPLACE WITH YOUR OWN CONSUMER KEY **** var client_id = 'YOUR_CONSUMER_KEY'; // Callback URL (must be HTTPS) configured for the connected app var redirect_uri = 'https://localhost/callback.html'; // Construct the redirect URL // For getting an id token, response_type of // "token id_token" (note the space), scope of // "openid", and some value for nonce is required. // client_id must be the consumer key of the connected app. // redirect_uri must match the callback URL configured for // the connected app. var url = 'https://login.salesforce.com/services/oauth2/authorize' + '?response_type=token id_token' + '&scope=openid' + '&nonce=abc' + '&client_id=' + client_id + '&redirect_uri=' + redirect_uri; // Redirect the user to Salesforce window.location.replace(url); } </script> </html>
callback.html
The callback.html is the page that the user sees when Salesforce redirects them to the app after sign in. It performs following steps:
- Extracts the ID token which is one of the parameters in the redirected URL.
- Creates a CognitoIdentityCredential object that abstracts the functionality of exchanging the ID token for Cognito token, exchanging the Cognito token for temporary AWS security credentials (by assuming the role), and using those credentials to set the default credentials for subsequent AWS calls.
- Lists the items in the DynamoDB table in your AWS account.
The following listing shows the complete markup and code for callback.html.
Note: Replace provider_url your Salesforce “Current My Domain URL” recorded in Task 1 and pool_id and role_arn with the values recorded in Task 2.
<!DOCTYPE html> <html> <head> <title>Cognito OIDC Sample</title> <script src="https://sdk.amazonaws.com/js/aws-sdk-2.850.0.min.js"></script> </head> <body onload="callback()"> <h2>Sample App - Amazon Cognito and OpenID Connect</h2> <p>Results after authentication with Salesforce:</p> <p> <span id="results" style="color:#FF0000"></span> </p> </body> <script type="text/javascript"> // Callback function called on page load function callback() { // Capture the redirected URL var url = window.location.href; // Check if there was an error parameter var error = url.match('error=([^&]*)') if (error) { // If so, extract the error description and display it var description = url.match('error_description=([^&]*)') printMessage('Error: ' + error[1] + '<br>Description: ' + description[1] + '</br>'); return; } // Extract id token from the id_token parameter var match = url.match('id_token=([^&]*)'); if (match) { var id_token = match[1]; // String captured by ([^&]*) // Make AWS request using the id token if (id_token) { printMessage('<span style="color:#000000">Using id token from Salesforce to query DynamoDB . . .</span>'); makeCognitoRequest(id_token); }else{ printMessage('Error: Could not retrieve id token from the URL'); } }else{ printMessage('Error: There was no id token in the URL'); } } // Make AWS request using Cognito // **** REPLACE provider_url with YOUR Salesforce ‘Current My Domain URL’ **** function makeCognitoRequest(id_token) { var aws_region = 'us-east-1'; var provider_url = 'login.salesforce.com'; var logins = {}; logins[provider_url] = id_token; // **** REPLACE pool_id and table_name WITH YOUR OWN VALUES **** var pool_id = 'us-east-1:11111111-aaaa-2222-bbbb-222222222222'; var table_name = 'ProductCatalog'; // Parameters required for CognitoIdentityCredentials var params = { IdentityPoolId: pool_id, Logins: logins }; // Amazon Cognito region AWS.config.region = aws_region; // Initialize CognitoIdentityCredentials AWS.config.credentials = new AWS.CognitoIdentityCredentials(params); // Cognito credentials AWS.config.credentials.get(function (err) { if (err) { // an error occurred printMessage(err); }else{ // successful response // DynamoDB client will automatically use the Cognito identity credentials provider var ddb = new AWS.DynamoDB(); // Scan the table ddb.scan({TableName: table_name}, function (err, data) { if (err){ // an error occurred printMessage(err); }else{ // successful response // Print the items var items = ''; for (i = 0; i < data.Count; i++) { items += data.Items[i].Id.N + ' '; } printMessage('<span style="color:#000000">Items found:</span>' + items); } }); } }); } // Print messages function printMessage(messageString){ document.getElementById("results").innerHTML = messageString; } </script> </html>
Task 5 – Test the sample app
Everything is now in place to test the sample app.
- On the computer where the web server is installed, point your web browser to https://localhost. Notice that the URL uses https, not http.
- In the index.html page, click Sign In with Salesforce.
. - Sign in with your Salesforce user name and password.
. - Click Allow to grant the sample app permission to get an ID token.
.
You are now authenticated using Salesforce and are redirected to the callback.html page. Assuming that your sign in was successful, you will see a list of items from the DynamoDB table. The app is able to access your AWS account because it assumes the role in your AWS account to get temporary AWS security credentials in exchange for the ID token provided by Salesforce.
- If you click Deny in Step 4, the next page will show User denied authorization error, because you didn’t grant the sample app permission to get ID token.
Conclusion
In summary, support for Open ID Connect expands the possible pools of identities you can choose from when building your AWS-powered apps. When combined with Amazon Cognito, you have a really simple way to bring your own identity, or integrate with any provider that supports this open standard.
If you have questions, please post them to the Cognito or IAM forums.
– Shon
Want more AWS Security how-to content, news, and feature announcements? Follow us on Twitter.