Validating Stateless Access_Tokens With the StatelessAccessTokenResolver

The StatelessAccessTokenResolver confirms that stateless access_tokens provided by AM are well-formed, have a valid issuer, have the expected access_token name, and have a valid signature.

After the StatelessAccessTokenResolver resolves an access_token, the OAuth2ResourceServerFilter checks that the token is within the expiry time, and that it provides the required scopes. For more information, see "StatelessAccessTokenResolver". This feature is supported with OpenAM 13.5, and AM 5 and later versions.

The following sections provide examples of how to validate signed and encrypted access_tokens:

Validating Signed Access_Tokens With the StatelessAccessTokenResolver and JwkSetSecretStore

This section provides examples of how to validate signed access_tokens with the StatelessAccessTokenResolver, using a JwkSetSecretStore. For more information about JwkSetSecretStore, see "JwkSetSecretStore".

Set Up AM
  1. Configure an OAuth 2.0 Authorization Provider:

    1. Select  Services, and add an OAuth 2.0 Provider.

    2. Accept all of the default values, and select Create. The service is added to the  Services list.

    3. On the Core tab, select the following option:

      • Use Client-Based Access & Refresh Tokens: on

    4. On the Advanced tab, select the following options:

      • Client Registration Scope Whitelist: myscope

      • OAuth2 Token Signing Algorithm: RS256

      • Encrypt Client-Based Tokens: Deselected

  2. Create an OAuth2 Client to request OAuth 2.0 access_tokens:

    1. Select Applications > OAuth 2.0 > Clients, and add a client with the following values:

      • Client ID: client-application

      • Client secret: password

      • Scope(s): myscope

    2. (From AM 6.5) On the Advanced tab, select the following values:

      • Grant Types: Resource Owner Password Credentials

      • Response Types: code token

    3. On the Signing and Encryption tab, include the following setting:

      • ID Token Signing Algorithm: RS256

Set Up IG
  1. Add the following route to IG:

    $HOME/.openig/config/routes/rs-stateless-signed.json
    %appdata%\OpenIG\config\routes\rs-stateless-signed.json
    {
      "name": "rs-stateless-signed",
      "condition": "${matches(request.uri.path, '/rs-stateless-signed')}",
      "heap": [
        {
          "name": "SecretsProvider-1",
          "type": "SecretsProvider",
          "config": {
            "stores": [
              {
                "type": "JwkSetSecretStore",
                "config": {
                  "jwkUrl": "http://openam.example.com:8088/openam/oauth2/connect/jwk_uri"
                }
              }
            ]
          }
        }
      ],
      "handler": {
        "type": "Chain",
        "capture": "all",
        "config": {
          "filters": [
            {
              "name": "OAuth2ResourceServerFilter-1",
              "type": "OAuth2ResourceServerFilter",
              "config": {
                "scopes": ["myscope"],
                "requireHttps": false,
                "accessTokenResolver": {
                  "type": "StatelessAccessTokenResolver",
                  "config": {
                    "secretsProvider": "SecretsProvider-1",
                    "issuer": "http://openam.example.com:8088/openam/oauth2",
                    "verificationSecretId": "any.value.in.regex.format"
                  }
                }
              }
            }
          ],
          "handler": {
            "type": "StaticResponseHandler",
            "config": {
              "status": 200,
              "headers": {
                "Content-Type": [ "text/html" ]
              },
              "entity": "<html><body><h2>Decoded access_token: ${contexts.oauth2.accessToken.info}</h2></body></html>"
            }
          }
        }
      }
    }
    
  2. Notice the following features of the route:

    • The route matches requests to /rs-stateless-signed.

    • A SecretsProvider in the heap declares a JwkSetSecretStore to manage secrets for signed access_tokens.

    • The JwkSetSecretStore specifies the URL to a JWK set on AM, that contains the signing keys.

    • The OAuth2ResourceServerFilter expects an OAuth 2.0 access_token in the header of the incoming authorization request, with the scope myscope.

    • The StatelessAccessTokenResolver uses the SecretsProvider to verify the signature of the provided access_token.

    • After the OAuth2ResourceServerFilter validates the access_token, it creates the OAuth2Context context. For more information, see "OAuth2Context".

    • If there is no access_token in a request, or token validation does not complete successfully, the filter returns an HTTP error status to the user-agent, and IG does not continue processing the request. This is done as specified in the RFC OAuth 2.0 Bearer Token Usage.

    • The StaticResponseHandler returns the content of the access_token from the context.

Test the Setup For a Signed Access_Token
  1. Get an access_token for the demo user, using the scope myscope:

    $ mytoken=$(curl -s \
    --user "client-application:password" \
    --data "grant_type=password&username=demo&password=Ch4ng31t&scope=myscope" \
    http://openam.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
  2. Display the token:

    $ echo ${mytoken}

    Note that the token is structured as a signed token.

  3. Access the route by providing the token returned in the previous step:

    $ curl -v http://openig.example.com:8080/rs-stateless-signed --header "Authorization: Bearer ${mytoken}"
    ...
    Decoded access_token: {
    sub=demo,
    cts=OAUTH2_STATELESS_GRANT,
    ...

Validating Signed Access_Tokens With the StatelessAccessTokenResolver and KeyStoreSecretStore

This section provides examples of how to validate signed access_tokens with the StatelessAccessTokenResolver, using a KeyStoreSecretStore. For more information about KeyStoreSecretStore, see "KeyStoreSecretStore".

Set Up Keys for Signing
  1. Locate the following directories for keys, keystores, and certificates, and in a terminal create variables for them:

    • Directory where the keystore is created: keystore_directory

    • AM keystore directory: am_keystore_directory

    • IG keystore directory: ig_keystore_directory

  2. Set up the keystore for signing keys:

    1. Generate a private key called signature-key, and a corresponding public certificate called x509certificate.pem:

      $ openssl req -x509 \
      -newkey rsa:2048 \
      -nodes \
      -subj "/CN=openig.example.com/OU=example/O=com/L=fr/ST=fr/C=fr" \
      -keyout $keystore_directory/signature-key.key \
      -out $keystore_directory/x509certificate.pem \
      -days 365
      ...
      writing new private key to '$keystore_directory/signature-key.key'
    2. Convert the private key and certificate files into a PKCS12 file, called signature-key, and store them in a keystore named keystore.p12:

      $ openssl pkcs12 \
      -export \
      -in $keystore_directory/x509certificate.pem \
      -inkey $keystore_directory/signature-key.key \
      -out $keystore_directory/keystore.p12 \
      -passout pass:password \
      -name signature-key
    3. List the keys in keystore.p12:

      $ keytool -list \
      -v \
      -keystore "$keystore_directory/keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      
      ...
      Your keystore contains 1 entry
      Alias name: signature-key

  3. Set up keys for AM:

    1. Copy the signing key keystore.p12 to AM:

      $ cp $keystore_directory/keystore.p12 $am_keystore_directory/AM_keystore.p12
    2. List the keys in the AM keystore:

      $ keytool -list \
      -v \
      -keystore "$am_keystore_directory/AM_keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      ...
      Your keystore contains 1 entry
      Alias name: signature-key
    3. Add a file called keystore.pass, with the content password:

      $ cd $am_keystore_directory
      $ echo -n password > keystore.pass

      The filename corresponds to the secret ID of the store password and entry password for the KeyStoreSecretStore.

    4. Restart AM.

  4. Set up keys for IG:

    1. Import the public certificate to the IG keystore, with the alias verification-key:

      $ keytool -import \
      -trustcacerts \
      -rfc \
      -alias verification-key \
      -file "$keystore_directory/x509certificate.pem" \
      -keystore "$ig_keystore_directory/IG_keystore.p12" \
      -storetype PKCS12 \
      -storepass "password"
      
      ...
      Trust this certificate? [no]:  yes
      Certificate was added to keystore
    2. List the keys in the IG keystore:

      $ keytool -list \
      -v \
      -keystore "$ig_keystore_directory/IG_keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      
      ...
      Your keystore contains 1 entry
      Alias name: verification-key
    3. In the IG configuration, set an environment variable for the keystore password:

      $ export KEYSTORE_SECRET_ID='cGFzc3dvcmQ='

    4. Restart IG.

Validate Signed Access_Tokens With the StatelessAccessTokenResolver and KeyStoreSecretStore
  1. Set up AM:

    1. Create a KeyStoreSecretStore to manage the new AM keystore:

      1. In AM, select  Secret Stores, and then add a secret store with the following values:

        • Secret Store ID: keystoresecretstore

        • Store Type: Keystore

        • File: am_keystore_directory/AM_keystore.p12

        • Keystore type: PKCS12

        • Store password secret ID: keystore.pass

        • Entry password secret ID: keystore.pass

      2. Select the Mappings tab, and add a mapping with the following values:

        • Secret ID: am.services.oauth2.stateless.signing.RSA

        • Aliases: signature-key

        The mapping sets signature-key as the active alias to use for signature generation.

    2. Create a FileSystemSecretStore to manage secrets for the KeyStoreSecretStore:

      1. select  Secret Stores, and then create a secret store with the following configuration:

        • Secret Store ID: filesystemsecretstore

        • Store Type: File System Secret Volumes

        • Directory: am_keystore_directory/secrets

        • File format: Plain text

    3. Configure an OAuth 2.0 Authorization Provider:

      1. Select  Services, and add an OAuth 2.0 Provider.

      2. Accept all of the default values, and select Create. The service is added to the  Services list.

      3. On the Core tab, select the following option:

        • Use Client-Based Access & Refresh Tokens: on

      4. On the Advanced tab, select the following options:

        • Client Registration Scope Whitelist: myscope

        • OAuth2 Token Signing Algorithm: RS256

        • Encrypt Client-Based Tokens: Deselected

    4. Create an OAuth2 Client to request OAuth 2.0 access_tokens:

      1. Select Applications > OAuth 2.0 > Clients, and add a client with the following values:

        • Client ID: client-application

        • Client secret: password

        • Scope(s): myscope

      2. (From AM 6.5) On the Advanced tab, select the following values:

        • Grant Types: Resource Owner Password Credentials

        • Response Types: code token

      3. On the Signing and Encryption tab, include the following setting:

        • ID Token Signing Algorithm: RS256

  2. Set up IG:

    1. Add the following route to IG, and replace ig_keystore_directory:

      $HOME/.openig/config/routes/rs-stateless-signed-ksss.json
      %appdata%\OpenIG\config\routes\rs-stateless-signed-ksss.json
      {
        "name": "rs-stateless-signed-ksss",
        "condition" : "${matches(request.uri.path, '/rs-stateless-signed-ksss')}",
        "heap": [
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          },
          {
            "name": "KeyStoreSecretStore-1",
            "type": "KeyStoreSecretStore",
            "config": {
              "file": "<ig_keystore_directory>/IG_keystore.p12",
              "storeType": "PKCS12",
              "storePassword": "keystore.secret.id",
              "keyEntryPassword": "keystore.secret.id",
              "secretsProvider": "SystemAndEnvSecretStore-1",
              "mappings": [
                {
                  "secretId": "stateless.access.token.verification.key",
                  "aliases": [ "verification-key" ]
                }
              ]
            }
          }
        ],
        "handler" : {
          "type" : "Chain",
          "capture" : "all",
          "config" : {
            "filters" : [ {
              "name" : "OAuth2ResourceServerFilter-1",
              "type" : "OAuth2ResourceServerFilter",
              "config" : {
                "scopes" : [ "myscope" ],
                "requireHttps" : false,
                "accessTokenResolver": {
                  "type": "StatelessAccessTokenResolver",
                  "config": {
                    "secretsProvider": "KeyStoreSecretStore-1",
                    "issuer": "http://openam.example.com:8088/openam/oauth2",
                    "verificationSecretId": "stateless.access.token.verification.key"
                  }
                }
              }
            } ],
            "handler": {
              "type": "StaticResponseHandler",
              "config": {
                "status": 200,
                "headers": {
                  "Content-Type": [ "text/html" ]
                },
                "entity": "<html><body><h2>Decoded access_token: ${contexts.oauth2.accessToken.info}</h2></body></html>"
              }
            }
          }
        }
      }
      

      Notice the following features of the route:

      • The route matches requests to /rs-stateless-signed-ksss.

      • The keystore password is provided by the SystemAndEnvSecretStore in the heap.

      • The OAuth2ResourceServerFilter expects an OAuth 2.0 access_token in the header of the incoming authorization request, with the scope myscope.

      • The accessTokenResolver uses a StatelessAccessTokenResolver to resolve and verify the authenticity of the access_token. The secret is provided by the KeyStoreSecretStore in the heap.

      • After the OAuth2ResourceServerFilter validates the access_token, it creates the OAuth2Context context. For more information, see "OAuth2Context".

      • If there is no access_token in a request, or if the token validation does not complete successfully, the filter returns an HTTP error status to the user-agent, and IG stops processing the request, as specified in the RFC, OAuth 2.0 Bearer Token Usage.

      • The StaticResponseHandler returns the content of the access_token from the context.

  3. Test the setup for a signed access_token:

    1. Get an access_token for the demo user, using the scope myscope:

      $ mytoken=$(curl -s \
      --user "client-application:password" \
      --data "grant_type=password&username=demo&password=Ch4ng31t&scope=myscope" \
      http://openam.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
    2. Display the token:

      $ echo ${mytoken}
    3. Access the route by providing the token returned in the previous step:

      $ curl -v http://openig.example.com:8080/rs-stateless-signed-ksss --header "Authorization: Bearer ${mytoken}"
      
      ...
      Decoded access_token: {
      sub=demo,
      cts=OAUTH2_STATELESS_GRANT,
      ...

Validating Encrypted Access_Tokens With the StatelessAccessTokenResolver and KeyStoreSecretStore

Set Up Keys for Encryption
  1. Locate the following directories for keys, keystores, and certificates, and in a terminal create variables for them:

    • Directory where the keystore is created: keystore_directory

    • AM keystore directory: am_keystore_directory

    • IG keystore directory: ig_keystore_directory

  2. Set up keys for AM:

    1. Generate the encryption key:

      $ keytool -genseckey \
      -alias encryption-key \
      -dname "CN=openig.example.com, OU=example, O=com, L=fr, ST=fr, C=fr" \
      -keystore "$am_keystore_directory/AM_keystore.p12" \
      -storetype PKCS12 \
      -storepass "password" \
      -keyalg AES \
      -keysize 256
    2. List the keys in the AM keystore:

      $ keytool -list \
      -v \
      -keystore "$am_keystore_directory/AM_keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      
      ...
      Your keystore contains 1 entry
      Alias name: encryption-key
    3. Add a file called keystore.pass, with the content password:

      $ cd $am_keystore_directory
      $ echo -n password > keystore.pass

      The filename corresponds to the secret ID of the store password and entry password for the KeyStoreSecretStore.

    4. Restart AM.

  3. Set up keys for IG:

    1. Import encryption-key into the IG keystore, with the alias decryption-key:

      $ keytool -importkeystore \
      -srcalias encryption-key \
      -srckeystore "$am_keystore_directory/AM_keystore.p12" \
      -srcstoretype PKCS12 \
      -srcstorepass "password" \
      -destkeystore "$ig_keystore_directory/IG_keystore.p12" \
      -deststoretype PKCS12 \
      -destalias decryption-key \
      -deststorepass "password" \
      -destkeypass "password"
    2. List the keys in the IG keystore:

      $ keytool -list \
      -v \
      -keystore "$ig_keystore_directory/IG_keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      
      ...
      Your keystore contains 1 entry
      Alias name: decryption-key
    3. In the IG configuration, set an environment variable for the keystore password:

      $ export KEYSTORE_SECRET_ID='cGFzc3dvcmQ='

    4. Restart IG.

Validate Encrypted Access_Tokens With the StatelessAccessTokenResolver and KeyStoreSecretStore
  1. Set up AM:

    1. Set up AM as described in "Validate Signed Access_Tokens With the StatelessAccessTokenResolver and KeyStoreSecretStore".

    2. Add a mapping for the encryption keystore:

      1. select  Secret Stores > keystoresecretstore.

      2. Select the Mappings tab, and add a mapping with the following values:

        • Secret ID: am.services.oauth2.stateless.token.encryption

        • Alias: encryption-key

    3. Enable token encryption on the OAuth 2.0 Authorization Provider:

      1. Select  Services > OAuth2 Provider.

      2. On the Advanced tab, select Encrypt Client-Based Tokens.

  2. Set up IG:

    1. Add the following route to IG, and replace ig_keystore_directory:

      $HOME/.openig/config/routes/rs-stateless-encrypted.json
      %appdata%\OpenIG\config\routes\rs-stateless-encrypted.json
      {
        "name": "rs-stateless-encrypted",
        "condition": "${matches(request.uri.path, '/rs-stateless-encrypted')}",
        "heap": [
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          },
          {
            "name": "KeyStoreSecretStore-1",
            "type": "KeyStoreSecretStore",
            "config": {
              "file": "<ig_keystore_directory>/IG_keystore.p12",
              "storeType": "PKCS12",
              "storePassword": "keystore.secret.id",
              "keyEntryPassword": "keystore.secret.id",
              "secretsProvider": "SystemAndEnvSecretStore-1",
              "mappings": [
                {
                  "secretId": "stateless.access.token.decryption.key",
                  "aliases": [ "decryption-key" ]
                }
              ]
            }
          }
        ],
        "handler": {
          "type": "Chain",
          "capture": "all",
          "config": {
            "filters": [ {
              "name": "OAuth2ResourceServerFilter-1",
              "type": "OAuth2ResourceServerFilter",
              "config": {
                "scopes": [ "myscope" ],
                "requireHttps": false,
                "accessTokenResolver": {
                  "type": "StatelessAccessTokenResolver",
                  "config": {
                    "secretsProvider": "KeyStoreSecretStore-1",
                    "issuer": "http://openam.example.com:8088/openam/oauth2",
                    "decryptionSecretId": "stateless.access.token.decryption.key"
                  }
                }
              }
            } ],
            "handler": {
              "type": "StaticResponseHandler",
              "config": {
                "status": 200,
                "headers": {
                  "Content-Type": [ "text/html" ]
                },
                "entity": "<html><body><h2>Decoded access_token: ${contexts.oauth2.accessToken.info}</h2></body></html>"
              }
            }
          }
        }
      }
      
    2. Notice the following features of the route compared to rs-stateless-signed.json, used in: "Validating Signed Access_Tokens With the StatelessAccessTokenResolver and KeyStoreSecretStore":

      • The route matches requests to /rs-stateless-encrypted.

      • The OAuth2ResourceServerFilter and KeyStoreSecretStore refer to the configuration for a decryption key instead of a verification key.

Test the Setup For an Encrypted Access_Token
  1. Get an access_token for the demo user, using the scope myscope:

    $ mytoken=$(curl -s \
    --user "client-application:password" \
    --data "grant_type=password&username=demo&password=Ch4ng31t&scope=myscope" \
    http://openam.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
  2. Display the token:

    $ echo ${mytoken}

    Note that the token is structured as an encrypted token.

  3. Access the route by providing the token returned in the previous step:

    $ curl -v http://openig.example.com:8080/rs-stateless-encrypted --header "Authorization: Bearer ${mytoken}"
    ...
    Decoded access_token: {
    sub=demo,
    cts=OAUTH2_STATELESS_GRANT,
    ...
Read a different version of :