Building ADFS Federation for your Web App using Amazon Cognito User Pools
March 31, 2023: An update to this post was published on the AWS Security Blog. Please see this post for the most up-to-date info.
User authentication and authorization can be challenging when you’re building web and mobile apps. The challenges include handling user data and passwords, token-based authentication, federating identities from external identity providers (IdPs), managing fine-grained permissions, scalability, and more.
In this blog post walkthrough, we show you how to federate identities from Windows Server Active Directory to authenticate users into your web app by leveraging AWS services. The main AWS service that we’ll use for this purpose is Amazon Cognito.
With Amazon Cognito user pools, you can seamlessly add user sign-up and sign-in to your mobile and web apps by using a secure and scalable user directory. In addition, you can federate users from a SAML IdP with Amazon Cognito user pools, map these users to a user directory, and get standard authentication tokens from a user pool after the user authenticates with a SAML IdP.
More specifically, this post explains how to integrate Amazon Cognito user pools with Active Directory Federation Services (AD FS) to obtain JWT tokens in your web app—that in turn can be used for downstream authentication. To demonstrate the end-to-end authentication flow, we’ve created a simple REST API that’s built on Amazon API Gateway. The REST API retrieves data from an Amazon DynamoDB table with the help of an AWS Lambda function. We’ll use those JWT tokens that are vended from user pools to authenticate to the REST API, which is hosted on API Gateway.
A great benefit of using Amazon Cognito user pools to federate users from a SAML provider is that a user pool supports SAML 2.0 post-binding endpoints. This eliminates the need for client-side parsing of the SAML assertion response, and the user pool directly receives the SAML response from your IdP through a user agent.
As part of the SAML federation feature, the user pool acts as a service provider on behalf of your application. The user pool becomes a single point of identity management for your application, and your application doesn’t need to integrate with multiple SAML IdPs.
Here’s the diagram of the authentication flow that we present throughout this blog:
The details of this flow are as follows:
- The app starts the sign-up and sign-in process by directing our user to the Cognito User Pools Hosted Web UI. For a mobile app, we can use a web view to show the hosted web UI. For the purposes of this article we are going to work with a web app that is hosted on Amazon S3 fronted by Amazon CloudFront.
- The Amazon Cognito user pool determines the appropriate IdP based on our configuration. For AD FS, the IdP is determined by the metadata file or metadata endpoint URL from our SAML IdP. For example, if we use AD FS, the metadata URL looks like: https://<yourservername>/FederationMetadata/2007-06/FederationMetadata.xml
- Our user is redirected to the identity provider—in this case Active Directory.
- The IdP authenticates the user if necessary. If the IdP recognizes that the user has an active session, the IdP skips the authentication to provide a single sign-in (SSO) experience.
- The IdP POSTs the SAML assertion to Amazon Cognito.
- The user’s profile is created within the user pool.
- After verifying the SAML assertion and collecting the user attributes (claims) from the assertion, Amazon Cognito returns OIDC tokens (ID, access and refresh tokens) to the app for user who is now signed in.
- The app then makes a GET request to the API Gateway passing along the JWT token for authorization. If authorized, the request is forwarded to AWS Lambda for data retrieval from DynamoDB.
End-to-end installation and configuration walkthrough
Let’s build the authentication flow that we described in the previous section step by step.
- Step 1: Install Active Directory and AD FS.
- Step 2: Create an Amazon Cognito user pool.
- Step 3: Configure Active Directory and AD FS.
- Step 4: Complete the Amazon Cognito configuration.
- Step 5: Deploy and configure the web app.
Step 1: Install Active Directory and ADFS
We will need to set up Active Directory and ADFS. For a walkthrough of how install both with an AWS CloudFormation template, see Enabling Federation to AWS Using Windows Active Directory, ADFS, and SAML 2.0. To complete this walkthrough, you will need to have a working Active Directory service, AD FS service and a user created within Active Directory.
Step 2: Create an Amazon Cognito User Pool
- Sign in to the Amazon Cognito console.
- On the Your User Pools page, choose Create a User Pool.
- For Pool name, type a name.
- Choose Step through settings.
- Choose Email address or phone number and then choose Allow email addresses. In addition, check email as a Required attribute.
- Choose Next step.
- In the Policies tab, choose the configurations you wish to enable.
- Choose Next step to advance to the Devices page.
- Under Do you want to remember your user’s devices? verify that No is selected.
- Choose Next step.
- Choose Add an app. Adding an app gives Amazon Cognito permission to call APIs that do not have an authenticated user.
- For App name, type a name.
- Clear the Generate client secret check box.
- Choose Create app.
- Choose Next step to advance to the Review page.
- Review your settings and choose Create pool.
- After the pool has been created, set up a domain name. This will be used during Step 3 (below). Go to the Domain name tab and choose a unique domain name. Click Save Changes.
Step 3: Configure Active Directory and ADFS
Now that you’ve created an Amazon Cognito user pool. you need to set up Amazon Cognito as a relying party in the SAML identity provider (in this case, ADFS). After you configure ADFS, you will return to Amazon Cognito to complete the final configurations for the application to work.
- Connect to the Windows Server instance where you have installed ADFS as an Administrator via RDP.
- Open the ADFS 2.0 console.
- According to the instructions in step 1, you should have created a user named bob in Active Directory with an email email@example.com. Make sure that bob has an email as shown in the screenshot below:
- Determine the URN for the Amazon Cognito user pool. The form of the URN is urn:amazon:cognito:sp:<user-pool-id>. You can find the user pool ID in the General settings tab.
- Configure ADFS to work with the Amazon Cognito user pool:
- Go to Trust Relationships > Relying Party Trusts > Add relying party trusts. This will start a wizard.
- Select Enter data about the relying party manually.
- Enter a display name for the relying party configuration.
- On the next screen, do not configure a certificate.
- Enable support for SAML 2.0 SSO service URL.
- Add the Amazon Cognito user pool URN as the relying party trust identifier.
- Configure the SAML POST binding. The SAML 2.0 post-binding endpoint (also known as the assertion consumer URL) for the Amazon Cognito user pool will be https://<domain-prefix>.auth.<region>.amazoncognito.com/saml2/idpresponse. This was configured as the Domain name in step 2.17.
- Select Permit all users to access this relying party.
- Choose Finish.
After you’ve finished the ADFS configuration, navigate to Trust Relationships > Relying Party Trusts. The screen should look similar to the following screenshot:
In a SAML federation, the IdP can pass various attributes about the user, the authentication method, or other points of context to the service provider (in this case, Amazon Cognito) in the form of SAML attributes. In ADFS, claim rules are used to assemble these required attributes using a combination of Active Directory lookups, simple transformations, and regular expression-based custom rules. In this example, you will configure two Claim Rules: Name ID and E-Mail.
The Edit Claim Rules window should already be open. If it isn’t, select your relying party trust from the Trust Relationships > Relying Party Trusts screen, then choose Edit Claim rules.
On the Configure Claim Rule page, type the following settings, then choose OK as shown in the following screenshot:
The Name ID Claim Rule should look like this:
Repeat the same steps for the E-mail Claim. The E-Mail Claim Rule should look like this:
The two claim rules should now be listed in the Issuance Transform Rules tab:
Before leaving ADFS configuration, download the metadata file for the ADFS. For AD FS, the metadata URL looks like: https://<servername>/FederationMetadata/2007-06/FederationMetadata.xml. The metadata file describes the endpoint of your SAML IdP (the ADFS service) to the service provider (Amazon Cognito).
Step 4: Complete the Amazon Cognito configuration
- Sign in to the Amazon Cognito console.
- Select the Amazon Cognito user pool we created earlier, then navigate to Federation > Identity providers and choose SAML.
- Choose Select file and upload the FederationMetadata.xml file you downloaded at the end of Step 3.
- Choose Create provider.
- Choose Attribute mapping. These mappings map the claims from the SAML assertion from AD FS to the user pool attributes. You configured an Email claim in ADFS, so you need to map this with the appropriate attribute in the user pool:
- SAML attribute: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
- User Pool attribute: email
Step 5: Deploy and configure a web app
In order to reduce the number of steps required to walk through this tutorial, we provided an AWS CloudFormation template to complete this deployment. The AWS CloudFormation template will deploy the architecture below:
This architecture is essentially step 8 from the authentication flow diagram earlier in this article. We have added Amazon S3 and Amazon CloudFront to the diagram, which is where your static website is hosted. Follow these steps to complete the walkthrough:
- Step 5.1: Create the AWS CloudFormation stack.
- Step 5.2: Manually integrate Amazon Cognito user pools with API Gateway.
- Step 5.3: Update the configuration for Amazon Cognito.
- Step 5.4: Update the configuration for the client-side application and upload to Amazon S3.
- Step 5.5: Insert one row into a DynamoDB table to help you test the application end-to-end.
Step 5.1: Create the AWS CloudFormation stack
Let’s deploy this infrastructure:
- Clone the sample code from my GitHub repository.
- Navigate to the AWS CloudFormation console in the region where you deployed the user pool, and choose Create Stack.
- Upload a template to Amazon S3 by choosing Browse. In the folder where you cloned the repository earlier, locate the prerequisites.yaml file.
- Provide a Stack name and a unique Bucketname. Choose Next, and select I acknowledge that AWS CloudFormation might create IAM resources with custom names.
- Finally, choose Create, and wait for all the resources to be deployed.
Step 5.2: Manually integrate the Amazon Cognito user pool with API Gateway
Go to the API Gateway console. After deploying the AWS CloudFormation template, you should see the following:
Under APIs, choose the DataManager API, and then choose Authorizers.
Choose Create new Authorizer, and then populate the relevant details, as shown in the following screenshot. You use the user pools authorizer to authenticate Get requests to our Rest API that’s hosted on API Gateway. Choose Create after all the details are populated. In the dropdown for Cognito User Pool, add the user pool that you created in Step 2.
Go back to Resources, choose GET, and then choose Method Request.
Add the authorizer that you just created, as shown in the following screenshot:
Step 5.3: Update the configuration for Amazon Cognito
Now you need to update the Amazon Cognito configuration based on the CloudFront distribution that the AWS CloudFormation template deployed in Step 5.1
Navigate to the AWS CloudFormation console and locate the CloudFormation stack that was deployed. In The Outputs tab, copy the values for CloudFrontEndpoint and DataManagerApiInvokeUrl as you will need both later.
Then navigate to the Amazon Cognito console and go to your user pool. Navigate to the App Client Settings tab, and populate all the details, as shown in the following screenshot:
Because our application interacts with Amazon Cognito via an OAuth 2.0 implicit flow, which requires a redirect, we need our website to use HTTPS. To have an HTTPS endpoint for the Amazon S3 static website, we can use the CloudFront distribution that was deployed by the AWS CloudFormation template in Step 5.1.
As soon as one of your users successfully logs in to the Active Directory infrastructure, the user is then automatically redirected to the callback URL. In this case, this is a CloudFront distribution URL with an Amazon Cognito ID, access token, and refresh token.
Step 5.4: Update the configuration for your client-side application, and upload it to Amazon S3
Navigate to the code that you previously cloned in step 5.1, and perform the following steps:
- With a file manager, navigate to the folder where the cloned content is located. Open the DataManager directory.
- Open the js folder. Using a text editor, open the config.js file.
- From the Amazon Cognito console, copy the client app application ID as the value of the userPoolClientId property. You can find the application ID in the App clients menu of the Amazon Cognito console.
- Change the value of the region property to the region that you are using (For example, us-east-2)
- While still in the Amazon Cognito console, open the Domain name page, and copy the custom prefix into the value for the authDomainPrefix property.
- Open the AWS CloudFormation console and choose the stack that was created in Step 5.1. With the stack selected, open the Outputs tab.
- Copy the value of the CloudfrontEndpoint output variable to the redirect_uri property.
- Copy the value of the DataManagerApiInvokeUrl output variable to the invokeUri property.
- Next, copy the files to the S3 bucket that hosts the static web site. You can use the AWS CLI or the Amazon S3 Console to upload the files.
If you want to use the AWS CLI, run the following command:
Step 5.5: Insert one row into the DynamoDB table to help test your application end to end
The AWS CloudFormation template that you used in Step 5.1 created the DynamoDB table that you can use to test your application. Add one row exactly like in the following screenshot so that you can get some results when you test your application end to end. The Lambda function that retrieves data from the ADFSSecretData DynamoDB table only retrieves data from rows where the email matches the one used to log in to Active Directory. To achieve this, you pass the event.requestContext.authorizer.claims.email.object within the Lambda function. This object contains the email that you used to log in to Active Directory.
Now you’re ready to test the end-to-end application.
Open the CloudFront URL in your browser and choose Enter. This should immediately take you to the web app landing page. From there, you’re automatically redirected to the Amazon Cognito hosted UI. You should see a screen like this:
After you choose your SAML provider, you should be redirected to your AD FS infratructure to get a login screen like this:
Note: If there’s an error, make sure that there’s a mapping in the hosts file for your AD FS server, with the appropriate hostname or public IP address of the EC2 instance where the AD FS infrastructure is hosted
Enter Bob’s email address for Username (like in the previous screenshot), and the password we defined in Active Directory. If the login is successful, you’re redirected back to the web app with a valid ID and access tokens, as shown in this screenshot:
If you click Refresh, you’ll get back the data that you stored in DynamoDB, as shown here:
In this walkthrough, you federated users from AD FS, and successfully authenticated those users to our REST API that’s hosted on API Gateway.
The SAML federation feature in Amazon Cognito helps you set up and integrate your apps with multiple SAML IdPs. By using Amazon Cognito’s SAML federation capabilities, your apps don’t need to handle the type of SAML IdP that they are interacting with. Amazon Cognito takes care of it on behalf of your application.
We hope this post helps with your authentication and authorization efforts. If you have questions or suggestions, please leave a comment.
About the Author
This article was written by Leo Drakopoulos, an AWS Solutions Architect.