Building an App Using Amazon Cognito and an OpenID Connect Identity Provider

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:

  1. The user accesses the sample app and clicks on Sign In with Salesforce button.
  2. The app redirects the user to Salesforce for signing in. After successful authentication, the app receives an ID token from Salesforce.
  3. The app exchanges the ID token for a Cognito token.
  4. The app exchanges the Cognito token for temporary AWS security credentials.
  5. 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:

Overview

Here is the list of tasks:

  1. Create a connected app in Salesforce
  2. Configure the AWS account
  3. Configure the web server
  4. Create the sample app
  5. 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.

  1. Log in to Salesforce at https://login.salesforce.com.
  2. Click Setup on the top-right corner.
  3. From the left-hand navigation pane, in the Build section, expand Create and click Apps.
  4. Scroll to the bottom until you see the Connected Apps section and click New.
  5. For Connected App Name, specify a name for the app e.g. Cognito OIDC Sample
  6. For Contact Email, specify a contact email address.
  7. Under API (Enable OAuth Settings), check the Enable OAuth Settings checkbox.
  8. 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
  9. For Selected OAuth Scopes, select Allow access to your unique identifier (openid) and click the Add button. When done, the page looks something like this:


     
  10. Click Save at the bottom of the page. Salesforce displays a summary of the new connected app.
  11. Make note of the Consumer Key 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.

  1. Sign in to the IAM Console.
  2. In the navigation pane select Identity Providers and then click Create Provider.
  3. For Provider Type, select OpenID Connect.
  4. For Provider URL, specify https://login.salesforce.com
  5. For Audience, specify the consumer key obtained in Task 1 and click Next Step.
  6. On the Verify Provider Information page, follow the instructions to verify the certificate of the provider and then click Create.
  7. On the next page, click Close to finish the wizard.
  8. From the list, click on the name of the provider just created (login.salesforce.com) to open the provider details page.
  9. 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.

  1. Sign in to the Cognito Console.
  2. Click Get Started Now or New Identity Pool if an identity pool already exists.
  3. For Identity Pool Name, specify a name for the pool e.g. Salesforce
  4. Under OpenID Connect Providers, select the provider created in the previous step and click Create Pool.
  5. On the next page, for IAM Role, choose Create a new IAM role and click Update Roles.
  6. On the next page, click Done.
  7. On the next page, in the top-right corner, click Edit Identity Pool.
  8. Make note of the Identity Pool ID because we will need it later when creating the sample app.

Finally, the access policy on the default role that got created in the previous step needs to be updated. This ensures that the sample app can access a DynamoDB table in the AWS account. 

  1. Sign in to the IAM Console.
  2. In the navigation pane select Roles and search for the default role that got created in the previous step. The name will be similar to Cognito_SalesforceAuth_DefaultRole.
  3. Click on the name of the role to open the role details page.
  4. Click Attach Role Policy.
  5. Select the Custom Policy radio button and click Select.
  6. For Policy Name, specify a name for the policy e.g. DynanmoDB_Scan
  7. For Policy Document, 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"

}] 
}
  1. Click Apply Policy.
  2. Make note of the Role ARN, 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.)

  1. On the computer that has IIS installed, create a self-signed certificate.
  2. Create a new folder named c:\oidc.
  3. Start IIS Manager by navigating to Start, All Programs, Administrative Tools, and Internet Information Services (IIS) Manager.
  4. In the left pane, right-click your computer name and select Add Web Site….
  5. For Site name, enter Cognito OIDC Sample
  6. For Physical path, enter c:\oidc.
  7. 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: 


     
  8. 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>
    <h1>Sample App - Amazon Cognito and OpenID Connect</h1>
    <p>
    This sample demonstrates how you can use <a href="http://aws.amazon.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://aws.amazon.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.aws.amazon.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 her to the app after sign in. It performs following steps:

  1. Extracts the ID token which is one of the parameters in the redirected URL.
  2. 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.
  3. 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 account_id with 12-digit AWS account number (no dashes) 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.0.16.min.js"></script>
</head>
<body onload="callback()">
    <h1>Sample App - Amazon Cognito and OpenID Connect</h1>
    <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
    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.

  1. On the computer where the web server is installed, point your web browser to https://localhost. Notice that the URL uses https, not http.
  2. In the index.html page, click Sign In with Salesforce.


     
  3. Sign in with your Salesforce user name and password.


     
  4. 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.


     
  5. 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

 

Comments