Transforming OpenID Connect ID Tokens Into SAML Assertions

OpenIG provides a token transformation filter to transform OpenID Connect ID tokens (id_tokens) issued by OpenAM into SAML 2.0 assertions. The implementation uses the REST Security Token Service (STS) APIs, where the subject confirmation method is Bearer.

Many enterprises use existing or legacy, SAML 2.0-based SSO, but many mobile and social applications are managed by OAuth 2.0/OpenID. Use the OpenIG token transformation filter to bridge OAuth 2.0 and SAML 2.0 frameworks.

About Token Transformation

The following figure illustrates the basic flow of information between a request, OpenIG, the OpenAM OAuth 2.0 and STS modules, and an application. For a more detailed view of the flow, see Flow of Events.

ttf schematic

The basic process is as follows:

  1. A user tries to access to a protected resource.

  2. If the user is not authenticated, the OAuth2ClientFilter redirects the request to OpenAM. After authentication, OpenAM asks for the user’s consent to give OpenIG access to private information.

  3. If the user consents, OpenAM returns an id_token to the OAuth2ClientFilter. The filter opens the id_token JWT and makes it available in attributes.openid.id_token and attributes.openid.id_token_claims for downstream filters.

  4. The TokenTransformationFilter redirects the request to the STS. The request includes the user’s credentials, the id_token to transform, and the location where the SAML 2.0 assertion is to be injected after successful transformation.

  5. The STS validates the signature, decodes the payload, and verifies that the user issued the transaction. The STS then issues a SAML assertion to OpenIG on behalf of the user.

  6. The SAML assertion is now available to be used by an application.

Installation Overview

This tutorial takes you through the steps to set up OpenAM as an OAuth 2.0 provider and OpenIG as an OpenID Connect relying party.

When a user makes a request, the OAuth2ClientFilter returns an id_token. The TokenTransformationFilter references a REST STS instance that you set up in OpenAM to transform the id_token into a SAML 2.0 assertion.

You need to be logged in to OpenAM as administrator to set up the configuration for token transformation, but you do not need special privileges to request a token transformation. For more information, see TokenTransformationFilter(5) in the Configuration Reference.

This tutorial is tested on OpenAM 13 and later. Before you start this tutorial:

Tasks for Configuring Token Transformation
Task See Section(s)

Create an OAuth 2.0 provider and OAuth 2.0/OpenID Connect Relying Party

Create a Bearer authentication module to validate the id_token and OpenAM user.

Create an STS REST instance to transform the id_token into a SAML assertion.

Create the routes in OpenIG to send the requests and test the setup.

Setting Up the OpenID Connect Provider and Client

In this part, you set up OpenAM as an OpenID Connect provider and OpenIG as an an OpenID Connect relying party. OpenIG authenticates with OpenAM and retrieves an OpenID Connect ID token (id_token) to be transformed by STS.

To Set Up a Sample User

In you haven’t set up the user George Costanza in a previous tutorial, follow this procedure.

  1. In the console for OpenAM 13 and later, select the top-level realm and browse to Subjects > User.

    In the console for OpenAM 12 and earlier, browse to Access Control > / (Top Level Realm) > Subjects > User.

  2. Click New and create a new user with the following values:

    • ID: george

    • Last Name: george

    • Full Name: george costanza

    • Password: costanza

  3. Click Save.

  4. In the User window, select the new user and set Email Address: costanza.

  5. Click Save.

To Set Up OpenID Connect Provider and Client
  1. Configure OpenAM as an OAuth 2.0 Authorization Server and OpenID Connect Provider.

    1. In the OpenAM console, select the top-level realm and browse to Configure OAuth Provider > Configure OpenID Connect.

    2. Accept the default values and click Create.

  2. Register OpenIG as an OpenID Connect relying party. This step enables OpenIG to communicate as an OAuth 2.0 relying party with OpenAM.

    1. In the OpenAM console, select the top-level realm and browse to Agents > OAuth 2.0/OpenID Connect Client.

    2. In the Agent table, click New, enter the following values, and then click Create:

      • Name: oidc_client_for_sts

      • Password: password

    You use these values for clientId and clientSecret in Creating a Bearer Module .

    1. In the Agent table, select the oidc_client_for_sts profile and add the following values:

      • Redirection URIs: http://openig.example.com:8080/id_token/callback

      • Scope(s): openid, profile, and email.

      • ID Token Signing Algorithm: HS256, HS384, or HS512.

        Because the algorithm must be HMAC, the default value of RS256 is not okay.

    2. Click Save.

Creating a Bearer Module

The OpenID Connect ID token bearer module expects an id_token in an HTTP request header. It validates the id_token, and, if successful, looks up the OpenAM user profile corresponding to the end user for whom the id_token was issued. Assuming the id_token is valid and the profile is found, the module authenticates the OpenAM user.

You configure the token bearer module to specify how OpenAM gets the information to validate the id_token, which request header contains the id_token, the identifier for the provider who issued the id_token, and how to map the id_token claims to an OpenAM user profile.

If you are using OpenAM 13.0, create the bearer with a curl command as described in To Create a Bearer Module for the id_token (OpenAM 13.0) . For later versions of OpenAM, use that procedure or follow the instructions in To Create a Bearer Module for the id_token (from OpenAM 13.5) .

To Create a Bearer Module for the id_token (OpenAM 13.0)
  1. In a terminal window, use a curl command similar to the following to retrieve the SSO token for your OpenAM installation.

    $ curl \
    --request POST \
    --header "Content-Type: application/json" \
    --header "X-OpenAM-Username: amadmin" \
    --header "X-OpenAM-Password: password" \
    --data "{}" \
    http://openam.example.com:8088/openam/json/authenticate
    
    
    "tokenId": "AQIC5w...NTcy*", "successUrl": "/openam/console" . . .

    For more information about using curl for OpenAM authentication, see the OpenAM Developer’s Guide.

  2. Replace <tokenId> in the following command with the tokenId returned by the previous step, and then run the command:

    $ curl -X POST -H "Content-Type: application/json" \
        -H "iplanetDirectoryPro: <tokenId>" \
        -d \
        '{
            "cryptoContextValue": "password",
            "jwtToLdapAttributeMappings": ["sub=uid", "email=mail"],
            "principalMapperClass": "org.forgerock.openam.authentication.modules.oidc.JwtAttributeMapper",
            "acceptedAuthorizedParties": ["oidc_client_for_sts"],
            "idTokenHeaderName": "oidc_id_token",
            "accountProviderClass": "org.forgerock.openam.authentication.modules.common.mapping.DefaultAccountProvider",
            "idTokenIssuer": "http://openam.example.com:8088/openam/oauth2",
            "cryptoContextType": "client_secret",
            "audienceName": "oidc_client_for_sts",
            "_id": "oidc"
        }' \
        http://openam.example.com:8088/openam/json/realm-config/authentication/modules/openidconnect?_action=create
    
    
    http://openam.example.com:8088/openam/json/realm-config/authentication/modules/openidconnect?_action=create
    {"principalMapperClass":"org.forgerock.openam.authentication.modules.oidc.JwtAttributeMapper", . . .

    The Bearer module is created in OpenAM. On the console of OpenAM 13.0, the module is displayed in Authentication > Modules but you cannot access its configuration page.

To Create a Bearer Module for the id_token (from OpenAM 13.5)
  1. In the OpenAM console, select the top-level realm and browse to Authentication > Modules.

  2. Select Add Module and create a new bearer module with the following characteristics:

    • Module name: oidc

    • Type: OpenID Connect id_token bearer

  3. In the configuration page, enter the following values and leave the other fields with the default values:

    • Audience name: oidc_client_for_sts, the name OAuth 2.0/OpenID Connect client.

    • List of accepted authorized parties: oidc_client_for_sts.

    • OpenID Connect validation configuration type: client_secret

    • OpenID Connect validation configuration value: password.

      This is the password of the OAuth 2.0/OpenID Connect client.

    • Name of OpenID Connect ID Token Issuer: http://openam.example.com:8088/openam/oauth2

  4. Select Save Changes.

Creating an Instance of STS REST

The REST STS instance exposes a preconfigured transformation under a specific REST endpoint. See the OpenAM documentation for more information about setting up a REST STS instance.

  1. In the OpenAM console, select the top-level realm and browse to STS.

  2. In Rest STS Instances, select Add, and then create a new instance with the following characteristics:

    • Deployment Configuration

      • Deployment Url Element: openig

        This value identifies the STS instance and is used by the instance parameter in the TokenTransformationFilter.

    • Issued SAML2 Token Configuration

      • SAML2 issuer Id: OpenAM

      • Service Provider Entity Id: openig_sp

      • NameIdFormat: Select nameid:format:transient

      • Attribute Mappings: Add password=mail and userName=uid.

      For STS, it isn’t necessary to create a SAML SP configuration in OpenAM.

    • OpenIdConnect Token Configuration

      • The id of the OpenIdConnect Token Provider: oidc

      • Token signature algorithm: The value must be consistent with the one you selected in To Set Up OpenID Connect Provider and Client , HMAC SHA 256

      • Client secret (for HMAC-signed-tokens): password

      • The audience for issued tokens: oidc_client_for_sts.

  3. Select Create.

  4. Log out of OpenAM.

Setting Up the Routes on OpenIG

The following sequence diagram shows what happens when you set up and access these routes.

ttf idtoken
To Set Up Routes to Create an id_token

Any errors that occur during the token transformation cause an error response to be returned to the client and an error message to be logged for the OpenIG administrator.

  1. Edit config.json to comment the baseURI in the top-level handler. The handler declaration appears as follows:

    {
        "handler": {
            "type": "Router",
            "audit": "global",
            "_baseURI": "http://app.example.com:8081",
            "capture": "all"
        }
    }

    Restart OpenIG for the changes to take effect.

  2. Add the following route to the OpenIG configuration as $HOME/.openig/config/routes/50-id-token.json

    On Windows, add the route as %appdata%$OpenIG\config\routes\50-id-token.json.

    {
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [
            {
              "type": "OAuth2ClientFilter",
              "config": {
                "clientEndpoint": "/id_token",
                "requireHttps": false,
                "requireLogin": true,
                "registrations": {
                  "name": "openam",
                  "type": "ClientRegistration",
                  "config": {
                    "clientId": "oidc_client_for_sts",
                    "clientSecret": "password",
                    "issuer": {
                      "type": "Issuer",
                      "config": {
                        "wellKnownEndpoint": "http://openam.example.com:8088/openam/oauth2/.well-known/openid-configuration"
                      }
                    },
                    "scopes": [
                      "openid",
                      "profile",
                      "email"
                    ]
                  }
                },
                "target": "${attributes.openid}",
                "failureHandler": {
                  "type": "StaticResponseHandler",
                  "config": {
                    "entity": "OAuth2ClientFilter failed...",
                    "reason": "NotFound",
                    "status": 500
                  }
                }
              }
            }
          ],
          "handler": {
            "type": "StaticResponseHandler",
            "config": {
              "entity": "{\"id_token\":\n\"${attributes.openid.id_token}\"} \n\n\n{\"saml_assertions\":\n\"${attributes.saml_assertions}\"}",
              "reason": "Found",
              "status": 200
            }
          }
        }
      },
      "condition": "${matches(request.uri.path, '^/id_token')}"
    }

    Notice the following features of the route:

    • The route matches requests to /id_token.

    • The OAuth2ClientFilter enables OpenIG to act as an OpenID Connect relying party.

      • The client endpoint is set to /id_token, so the service URIs for this filter on the OpenIG server are /openid/login, /openid/logout, and /openid/callback.

      • For convenience in this test, "requireHttps" is false. In production environments, set it to true. So that you see the delegated authorization process when you make a request, "requireLogin" is true.

      • The registration parameter holds configuration parameters provided during To Set Up OpenID Connect Provider and Client . OpenIG uses these parameters to connect with OpenAM.

      • The target for storing authorization state information is ${attributes.openid}. This is where subsequent filters and handlers can find access tokens and user information.

    • When the request succeeds, a StaticResponseHandler displays the id_token and a placeholder for the SAML assertion.

  3. With OpenIG running, access http://openig.example.com:8080/id_token.

    The OpenAM login screen is displayed.

  4. Log in to OpenAM with the username george and password costanza.

    An OpenID Connect request to access private information is displayed.

  5. Select Allow.

    The id_token is displayed above an empty placeholder for the SAML assertion.

    {"id_token":
    "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAiYXRfaGFzaCI6ICJ . . ."}
    
    {"saml_assertions":
    ""}
To Edit the Route to Transform the id_token Into a SAML Assertion
  1. Add the following filter at the end of chain in 50-id-token.json. An example of the edited route is at the end of this procedure.

    {
      "type": "TokenTransformationFilter",
      "config": {
        "openamUri": "http://openam.example.com:8088/openam",
        "username": "george",
        "password": "costanza",
        "idToken": "${attributes.openid.id_token}",
        "target": "${attributes.saml_assertions}",
        "instance": "openig",
        "ssoTokenHeader": "iPlanetDirectoryPro"
      }
    }

    Notice the following features of the new filter:

    • Requests from this filter are made to http://openam.example.com:8088/openam.

    • The username and password are for OpenAM subject set up in To Set Up OpenID Connect Provider and Client .

    • The id_token parameter defines where this filter gets the id_token created by the OAuth2ClientFilter.

    • The target parameter defines where this filter injects the SAML 2.0 assertion after transforming the id_token.

    • The instance parameter must match the Deployment URL Element for the REST STS instance.

  2. With OpenIG running, access http://openig.example.com:8080/id_token.

    The SAML assertion is displayed under the id_token.

    {"id_token":
    "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAiYXRfaGFzaCI6ICJ . . ."}
    
    {"saml_assertions":
    <"saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version= . . ."}

Example of the final id_token.json:

{
  "handler": {
    "type": "Chain",
    "config": {
      "filters": [
        {
          "type": "OAuth2ClientFilter",
          "config": {
            "clientEndpoint": "/id_token",
            "requireHttps": false,
            "requireLogin": true,
            "registrations": {
              "name": "openam",
              "type": "ClientRegistration",
              "config": {
                "clientId": "oidc_client_for_sts",
                "clientSecret": "password",
                "issuer": {
                  "type": "Issuer",
                  "config": {
                    "wellKnownEndpoint": "http://openam.example.com:8088/openam/oauth2/.well-known/openid-configuration"
                  }
                },
                "scopes": [
                  "openid",
                  "profile",
                  "email"
                ]
              }
            },
            "target": "${attributes.openid}",
            "failureHandler": {
              "type": "StaticResponseHandler",
              "config": {
                "entity": "OAuth2ClientFilter failed...",
                "reason": "NotFound",
                "status": 500
              }
            }
          }
        },
        {
          "type": "TokenTransformationFilter",
          "config": {
            "openamUri": "http://openam.example.com:8088/openam",
            "username": "george",
            "password": "costanza",
            "idToken": "${attributes.openid.id_token}",
            "target": "${attributes.saml_assertions}",
            "instance": "openig",
            "amHandler": {
              "type": "ClientHandler"
            },
            "ssoTokenHeader": "iPlanetDirectoryPro"
          }
        }
      ],
      "handler": {
        "type": "StaticResponseHandler",
        "config": {
          "entity": "{\"id_token\":\n\"${attributes.openid.id_token}\"} \n\n\n{\"saml_assertions\":\n\"${attributes.saml_assertions}\"}",
          "reason": "Found",
          "status": 200
        }
      }
    }
  },
  "condition": "${matches(request.uri.path, '^/id_token')}"
}