Federation between AWS Cognito — ForgeRock OpenAM using OpenID Connect (OIDC) & SAML
AWS Cognito is a fully managed service that provides a secure user directory that scales to hundreds of millions of users. It also provides sign in through social identity providers such as Google, Facebook, and Amazon, and through enterprise identity providers via SAML. Amazon Cognito also provides solutions to control access to backend AWS resources from your mobile or web app. You can define roles and map users to different roles so your app can access only the resources that are authorized for each user.
Use Case:
Let us consider an organization which uses ForgeRock OpenAM as the enterprise Identity and Access Management solution . Organization has lots of applications hosted on-premise as well as in cloud. For applications that are using cloud based services, users should be able to single sign-on via AWS Cognito, ForgeRock OpenAM and securely access the various cloud services. In a digital world, it is common for a user to access multiple apps and services across on-premise and cloud. The main challenge is to provide single sign-on capabilities and implement authorization controls.
Architecture:
We have to setup a OIDC integration between the client app to Cognito and SAML federation between AWS Cognito to ForgeRock OpenAM.
- User initiates login process from mobile / web app.
- Mobile / web app makes a OIDC Authorization code grant flow with openid + profile scope to AWS Cognito
- AWS Cognito automatically posts a SAML Authn request to ForgeRock OpenAM via browser
- OpenAM will redirect the user to login page.
- User enters the credentials.
- If the credentials are valid, OpenAM will post a SAML assertion back to AWS Cognito via browser
- AWS Cognito will create / update the local user profile in Cognito’s user pool
- AWS Cognito will return a authorization code to the redirect_uri which is the web app or mobile app URL
- Mobile / web app makes a /token API call to AWS Cognito with authorization code, client_id and client secret.
- If the authorization code and client credentials are valid, AWS Cognito will return access_token, refresh_token and id_token to the client application.
- Mobile / web app will complete the login process using these tokens and show the home page.
There are few prerequisites for setting up this integration:
- AWS account — Free tier or business account
- ForgeRock OpenAM in local machine (any version > 13)
- Knowledge on OAuth2, OpenID Connect and SAML protocols
We have to setup the following components:
- Local ForgeRock OpenAM instance and SAML IDP setup
- AWS Cognito user pool setup
- SAML Federation between AWS Cognito and OpenAM
Step 1 : Local ForgeRock OpenAM instance and SAML IDP setup
- Add a local hosts file entry for 127.0.0.1 openam.example.com
- Download the OpenAM 13.5 war from ForgeRock website and rename it to idpam.war
- Follow this guide to setup a local OpenAM instance. Use http://openam.example.com:8080/idpam as the FQDN instead of http://openam.example.com:8080/openam
- Create a new realm called “AWS”
- Follow OpenAM admin guide to configure a Hosted IDP in AWS realm and for step #5 alone, follow below instructions:
Add the below attribute mapping:mail = mail
cn = cn
sn = sn
givenname = givenname
- If the setup is successful, you should be able to access the IDP metadata in this URL
- You can use the below curl command to download this metadata as idpmetadata.xml file
curl --output idpmetadata.xml "http://openam.example.com:8080/idpam/saml2/jsp/exportmetadata.jsp?entityid=http://openam.example.com:8080/idpam&realm=/AWS"
- Navigate to “Realms > AWS > Subjects” tab and create a new user with below details:
- After creating, select that user again and update the email address as “idpuser@example.com”
Step 2 : Configure AWS Cognito user pool
- Login to AWS Management console and navigate to Cognito. Select “Create a user pool”
- Enter pool name as openamuserpool and select “Review Defaults” option.
- In Review screen, select “Choose username attributes…” link
- In this screen, select “Email address or phone number” option and below that select “Allow email address” option
- Again, select Review from left navigation bar and that should have the below configuration
- Go ahead and click “Create pool” to create this user pool
- Note down the “Pool Id” value which will start with the region name like “us-east-1_xxxxx”
- Select “App integration > Domain name” in the left navigation bar
- Enter a “Domain prefix” value. It can be any value as long as it is available. Save the changes.
- Go to “General Settings > App clients” and click “Add an app client”. Name it as openamagent.
- You can enter any name for the agent. Make sure “Generate client secret” is checked and click “Create app client” button
- Note down the “App client id” and “App client secret” value.
- Go to “Federation > Identity providers > SAML” . Select the location of “idpmetadata.xml” file downloaded during OpenAM IDP setup. Enter a provider name as “openam” and identifier as openam.example.com. Then create the provider.
- Go to “Federation > Attribute Mapping > SAML” and enter the following mappings. Save the changes.
- Go to “App integration > App client settings” and it should automatically select the openamagent.
- Select “openam” in “Enabled Identity Providers”, enter a dummy “Callback URL(s)” like “http://localhost/callback”, select “Authorization code grant” , select “openid” and “profile” scopes. Save the changes.
Note : In reality, “Callback URL(s)” will be the mobile / web app URL which will process the authentication response from Cognito and allow the user to login
Step 3 : Setup AWS Cognito SP in OpenAM
- Prepare the SP metadata xml file for Cognito SP. Use the below template for the SP Metadata and save it as spaws.xml file. Replace the <pool id> and <domain URL> value accordingly. <pool id> is available in “General Settings > Pool Id” and <domain URL> is available in “App integration > Domain”
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<EntityDescriptor entityID="urn:amazon:cognito:sp:<pool id>" xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
<SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<AssertionConsumerService index="0" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="<domain URL>/saml2/idpresponse"/>
</SPSSODescriptor>
</EntityDescriptor>
- Login to OpenAM admin console “http://openam.example.com:8080/idpam” and navigate to “Realms > AWS”.
- Setup Remote service provider following this instruction (refer below image). You can skip Optional step 4. Click “Configure” button and it should setup the Cognito SP.
Step 4 : Testing
- To test these changes, open a new browser session and initiate Authorization code grant flow from a browser. Use the below URL. Replace the <domainURL> and <openamagentappclientid> from Cognito accordingly.
https://<domainURL>/oauth2/authorize?response_type=code&client_id=<openamagentappclientid>&redirect_uri=http://localhost/callback&scope=openid+profile&idp_identifier=openam.example.com
- This URL should automatically redirect the user to OpenAM login page. You can open some tool like “Mozilla SAML tracer” to monitor the SAML request and assertion response
- Enter “idpuser1” credentials which was created during OpenAM setup. If the credentials are valid, OpenAM will post a SAML assertion back to Cognito which in turn will redirect the user to “Callback URL” with authorization code. Final URL in the browser will look like this “http://localhost/callback?code=69842cc6-b7ea-4661-85af-19f7a43f24ef” and code value will vary every user login.
- If you had monitored the SAML flow using a SAML tracer, you should see the below Attributes in SAML assertion:
<saml:AttributeStatement>
<saml:AttributeStatement>
<saml:Attribute Name="sn">
<saml:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string"
>user
</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="cn">
<saml:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="xs:string"
>IDP user
</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="mail">
<saml:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="xs:string"
>idpuser@example.com
</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="givenname">
<saml:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="xs:string"
>ipd
</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
- Also, if you had observed the request and response flow in a tracer, you will see that OpenAM posts an assertion to Cognito which in turn redirects to “Callback URL” with the authorization code
- Now, execute the below curl command to get access_token, refresh_token and id_token. Replace the following values
- <app client id>:<app client secret> : This is available in Cognito “General settings > App clients”
- <Domain URL> : This is available in Cognito “App integration”
- <authorization code> : You can get this from the URL query string code parameter
Note : Authorization code has very less expiry time for security. In case you execute this curl command after 5 minutes or so, it will return {“error”:”invalid_grant”}. In this case, you have to again go through the browser flow and get a new authorization code. If the session is valid, it will automatically login and redirect to app URL with a code. Also, please note that an authorization code cannot be reused i.e. once you get the tokens, it will become invalid. This is for security purpose.
curl -X POST --user <app client id>:<app client secret> '<Domain URL>/oauth2/token?grant_type=authorization_code&scope=openid+profile&redirect_uri=http://localhost/callback&code=<authorization code>' -H 'Content-Type: application/x-www-form-urlencoded'{
"id_token": "..........",
"access_token": "...........",
"refresh_token": "........",
"expires_in": 3600,
"token_type": "Bearer"
}
- Both id_token and access_token are JWT tokens. You can check the values in https://jwt.io/
- Also, go to Cognito “openamuserpool > General Settings > Users and groups” and then refresh the “Users” table. You will see a new entry. Click that entry and you should see all the attributes that were set in OpenAM.
- Now, let us test another scenario. Login to OpenAM and modify idpuser1 Full name to “IDP user modified”
- Remove all the openam and amazon domain cookies. Initiate the login flow again using the authorization grant flow and login as idpuser1.
- If the login is successful, go to Cognito “Users and groups” and check the value of name attribute of this user. It should have changed to “ IDP user modified”. Please refresh the profile once if it doesn’t show up.
We have successfully setup a federation between Mobile / web app to AWS Cognito using ForgeRock OpenAM as the IdP. This functionality can be extended further by defining Cognito Identity pool and defining roles for each user based on the id_token claims. This will be really useful in a digital world where users access various services.
You might be interested in this blog which takes this setup further. It talks about mapping OpenAM roles to AWS Cognito Role-Based Access control and access AWS services like S3, API Gateway etc.
Thanks for reading this blog. If you have any questions / suggestions, kindly leave a comment.