OIDC ID tokens to SAML assertions
This page builds on the example in OpenID Connect to transform OpenID Connect ID tokens into SAML 2.0 assertions.
Many enterprises use existing or legacy, SAML 2.0-based SSO, but many mobile and social applications are managed by OpenID Connect. Use the PingGateway TokenTransformationFilter to bridge the gap between OpenID Connect and SAML 2.0 frameworks.
The following figure illustrates the data flow:
-
A user tries to access a protected resource.
-
If the user isn’t authenticated, the
AuthorizationCodeOAuth2ClientFilter
redirects the request to AM. After authentication, AM asks for the user’s consent to give PingGateway access to private information. -
If the user consents, AM returns an
id_token
to theAuthorizationCodeOAuth2ClientFilter
. The filter opens theid_token
JWT and makes it available inattributes.openid.id_token
andattributes.openid.id_token_claims
for downstream filters. -
The
TokenTransformationFilter
calls the AM STS to transform theid_token
into a SAML 2.0 assertion. -
The STS validates the signature, decodes the payload, and verifies the user issued the transaction. The STS then issues a SAML assertion to PingGateway on behalf of the user.
-
The
TokenTransformationFilter
puts the SAML assertion in${contexts.sts.issuedToken}
.
The following sequence diagram shows a more detailed view of the flow:
Update AM settings
Before you start, set up and test the example in AM as OIDC provider.
The following example uses an authentication tree for the Security Token Service (STS) to validate the incoming ID token and map its subject to an account.
Token signing
Change the token signature algorithm in the oidc_client
profile.
-
In the AM admin UI, select Applications > OAuth 2.0 > Clients >
oidc_client
. -
Under Signing and Encryption, set ID Token Signing Algorithm to
HS256
and click Save Changes.
Client secret
Store the client secret in a secret store.
-
Go to http://am.example.com:8088/openam/encode.jsp and use the page to encode the client secret,
password
. -
Write the result to a AM default password secret store file named
clientpass
:$ echo -n <encoded-client-secret> > /path/to/openam/security/secrets/encrypted/clientpass
In production, use your own secret store. |
ID token normalization script
Create a script to normalize the ID token for the STS.
-
Select Scripts > + New Script.
-
Create a script with the following settings:
-
Name:
Normalize id_token
-
Script Type:
Social Identity Provider Profile Transformation
-
Language:
JavaScript
-
Evaluator Version:
Legacy
-
-
In the script editor, add the following JavaScript and click Save Changes:
// The ID token "subname" claim holds the account username: (function () { var fr = JavaImporter( org.forgerock.json.JsonValue); var identity = fr.JsonValue.json(fr.JsonValue.object()); identity.put('userName', jwtClaims.get('subname')); return identity; }());
Account mapping script
Create a script to map the normalized ID token to the AM account username.
-
Select Scripts > + New Script.
-
Create a script with the following settings:
-
Name:
Add username to shared state
-
Script Type:
Decision node script for authentication trees
-
Evaluator Version:
Next Generation
-
-
In the script editor, add the following JavaScript and click Save Changes:
// Get the username from the identity returned by the previous script: var attributes = nodeState.get("lookupAttributes"); var userName = attributes.get("userName"); nodeState.putShared("username", userName); action.goTo('true');
Tree for STS
Add a tree to validate the incoming ID token and map its subject to the AM demo user account.
-
Select Authentication > Trees > + Create Tree.
-
Name the new tree
TransformIdToken
. -
Add nodes to the tree as in the following image:
-
The OIDC ID Token Validator node lets the STS validate the incoming ID token.
The node has the following non-default settings:
-
OpenID Connect Validation Type:
Client Secret
-
OpenID Connect Validation Value:
password
(Although required, the value isn’t used.) -
Client Secret Label:
clientpass
-
Token Issuer:
http://am.example.com:8088/openam/oauth2
-
Audience name:
oidc_client
-
Authorized parties:
oidc_client
-
Transformation Script:
Normalize id_token
-
-
The Scripted Decision node maps the ID token subject claim to an AM username.
The node has the following non-default settings:
-
Script:
Add username to shared state
-
Outcomes:
true
-
-
-
Click Save.
Add REST STS
Add a REST STS instance to transform the ID token to a SAML v2.0 assertion.
-
Click STS > + Add Rest STS, add the following non-default settings:
- Deployment URL Element
-
openig
(must match the TokenTransformationFilter"instance"
) - Deployment
-
-
Authentication Target Mappings: replace the existing
OPENIDCONNECT|…
value with:OPENIDCONNECT|service|TransformIdToken|oidc_id_token_auth_target_header_key=oidc_id_token
-
- SAML2 Token
-
For STS, it isn’t necessary to create a SAML SP configuration in AM. -
SAML2 issuer Id:
OpenAM
-
Service Provider Entity Id:
openig_sp
-
NameIdFormat:
urn:oasis:names:tc:SAML:2.0:nameid-format:transient
-
- OpenID Connect Token
-
-
OpenID Connect Token Provider Issuer Id:
oidc
-
Token signature algorithm:
HMAC SHA 256
-
Client Secret:
password
-
Issued Tokens Audience:
oidc_client
-
-
Click Create.
-
Under SAML 2 Token on the new STS instance, add the following Attribute Mappings:
-
Key:
userName
, Value:uid
-
Key:
password
, Value:mail
-
-
Click Save Changes.
Configure PingGateway
-
Set environment variables for
oidc_client
andig_agent
, then restart PingGateway:$ export OIDC_SECRET_ID='cGFzc3dvcmQ=' $ export AGENT_SECRET_ID='cGFzc3dvcmQ='
-
Add the following route to PingGateway:
-
Linux
-
Windows
$HOME/.openig/config/routes/50-idtoken.json
%appdata%\OpenIG\config\routes\50-idtoken.json
{ "name": "50-idtoken", "baseURI": "http://app.example.com:8081", "condition": "${find(request.uri.path, '^/home/id_token')}", "heap": [ { "name": "SystemAndEnvSecretStore-1", "type": "SystemAndEnvSecretStore" }, { "name": "AuthenticatedRegistrationHandler-1", "type": "Chain", "config": { "filters": [ { "name": "ClientSecretBasicAuthenticationFilter-1", "type": "ClientSecretBasicAuthenticationFilter", "config": { "clientId": "oidc_client", "clientSecretId": "oidc.secret.id", "secretsProvider": "SystemAndEnvSecretStore-1" } } ], "handler": "ForgeRockClientHandler" } }, { "name": "AmService-1", "type": "AmService", "config": { "agent": { "username": "ig_agent", "passwordSecretId": "agent.secret.id" }, "secretsProvider": "SystemAndEnvSecretStore-1", "url": "http://am.example.com:8088/openam/" } } ], "handler": { "type": "Chain", "config": { "filters": [ { "name": "AuthorizationCodeOAuth2ClientFilter-1", "type": "AuthorizationCodeOAuth2ClientFilter", "config": { "clientEndpoint": "/home/id_token", "failureHandler": { "type": "StaticResponseHandler", "config": { "status": 500, "headers": { "Content-Type": [ "text/plain" ] }, "entity": "An error occurred during the OAuth2 setup." } }, "registrations": [ { "name": "oidc-user-info-client", "type": "ClientRegistration", "config": { "clientId": "oidc_client", "issuer": { "name": "Issuer", "type": "Issuer", "config": { "wellKnownEndpoint": "http://am.example.com:8088/openam/oauth2/.well-known/openid-configuration" } }, "clientSecretIdUsage": "ID_TOKEN_VALIDATION_ONLY", "secretsProvider": "SystemAndEnvSecretStore-1", "clientSecretId": "oidc.secret.id", "scopes": [ "openid", "profile", "email" ], "authenticatedRegistrationHandler": "AuthenticatedRegistrationHandler-1" } } ], "requireHttps": false, "cacheExpiration": "disabled" } }, { "name": "TokenTransformationFilter-1", "type": "TokenTransformationFilter", "config": { "idToken": "${attributes.openid.id_token}", "instance": "openig", "amService": "AmService-1" } } ], "handler": { "type": "StaticResponseHandler", "config": { "status": 200, "headers": { "Content-Type": [ "text/plain; charset=UTF-8" ] }, "entity": "{\"id_token\":\n\"${attributes.openid.id_token}\"} \n\n\n{\"saml_assertions\":\n\"${contexts.sts.issuedToken}\"}" } } } } }
Learn how to set up the PingGateway route in Studio in Token transformation in Structured Editor.
Notice the following features of the route:
-
The route matches requests to
/home/id_token
. -
The
AmService
in the heap is used for authentication and REST STS requests. -
The
AuthorizationCodeOAuth2ClientFilter
enables PingGateway to act as an OIDC relying party:-
The client endpoint is
/home/id_token
, so the service URIs for this filter in PingGateway are/home/id_token/login
,/home/id_token/logout
, and/home/id_token/callback
. -
For convenience in this test,
requireHttps
is false. -
The target for storing authorization state information is
${attributes.openid}
. Later filters and handlers can find access tokens and user information at this target.
-
-
The
ClientRegistration
holds configuration provided in AM as OIDC provider. PingGateway uses it to connect to AM and verify the ID token signature with the client secret. -
The
TokenTransformationFilter
transforms an ID token into a SAML assertion:-
The
id_token
parameter defines where this filter gets the ID token.The
TokenTransformationFilter
puts the result in${contexts.sts.issuedToken}
.
-
-
On success, a
StaticResponseHandler
displays the ID token and SAML assertion.
-
Validation
-
In your browser’s privacy or incognito mode, go to https://ig.example.com:8443/home/id_token.
AM displays the login screen.
-
Log in to AM as username
demo
, passwordCh4ng31t
.AM prompts you to allow access to your account information.
-
Select Allow.
The
StaticResponseHandler
displays the ID token and SAML assertion:{"id_token": "<id-token>"} {"saml_assertions": "…<saml:Subject><saml:NameID…>demo</saml:NameID>…"}
If a request returns an HTTP 414 URI Too Long error, refer to URI Too Long error. |