JwtBuilderFilter

Collects data at runtime, packs it in a JSON Web Token (JWT), and places the resulting JWT into the "JwtBuilderContext". Use this filter with a HeaderFilter for a flexible way to pass identity or other runtime information to the protected application.

Configure JwtBuilderFilter to create an unsigned JWT, a signed JWT, or a signed then encrypted JWT:

  • Sign the JWT so that an application can validate the authenticity of the claims/data. The JWT can be signed with a shared secret or private key, and verified with a shared secret or corresponding public key.

  • Encrypt the JWT to reduce the risk of a data breach.

To help with development, the sample app includes a /jwt endpoint to display the JWT, verify its signature, and decrypt the JWT.

Usage

{
  "name": string,
  "type": "JwtBuilderFilter",
  "config": {
    "template": map or runtime expression<map>,
    "secretsProvider": SecretsProvider reference,
    "signature": object
  }
}

Properties

"template": map or runtime expression<map>, required

A map of information taken from the request or associated contexts in IG.

If this property is a map, the structure must have the format Map<String, Object>. For example,

"template": {
  "name": "${contexts.userProfile.commonName}",
  "email": "${contexts.userProfile.rawInfo.mail[0]}",
  "address": "${contexts.userProfile.rawInfo.postalAddress[0]}",
  "phone": "${contexts.userProfile.rawInfo.telephoneNumber[0]}"
}

If this property is an expression, its evaluation must give an object of type Map<String, Object>. For example,

"template": "${contexts.attributes}"

See also "Expressions".

"secretsProvider": SecretsProvider reference, optional

The SecretsProvider object to query for JWT signing or encryption keys. For more information, see "SecretsProvider".

Default: The route's default secret service. For more information, see "Default Secrets Object".

"signature": object, optional

A JWT signature to allow the authenticity of the claims/data to be validated. A signed JWT can be encrypted.

{
  "signature": {
    "secretId":  configuration expression<secret-id>,
    "algorithm": configuration expression<string>,
    "encryption": object
  }
}
"secretId": configuration expression<secret-id>, required if signature is used

The secret ID of the key used to sign the JWT.

For information about supported formats for secret-id, see secret-id.

"algorithm": expression<string>, optional

The algorithm with which to sign the JWT.

The following algorithms are supported but not necessarily tested in IG:

Default: RS256

"encryption": object, optional

Encrypt the JWT.

{
  "encryption": {
    "secretId": configuration expression<secret-id>,
    "algorithm": configuration expression<string>,
    "method": configuration expression<enumeration>
  }
}
"secretId": configuration expression<secret-id>, optional

The secret ID of the key used to encrypt the JWT. The value is mapped to key aliases in "KeyStoreSecretStore".

For information about supported formats for secret-id, see secret-id.

"algorithm": expression<string>, required

The algorithm used to encrypt the JWT.

For information about available algorithms, see "alg" (Algorithm) Header Parameter Values for JWE .

"method": configuration expression<enumeration>, required

The method used to encrypt the JWT.

For information about available methods, see "enc" (Encryption Algorithm) Header Parameter Values for JWE .

Examples

Packing Data Into a JWT

In this example, the JwtBuilderFilter takes the username and email from the UserProfileContext, and stores them in an unsigned, unencrypted JWT.

Before you start:

  1. Select Applications > Agents > Identity Gateway, add an agent with the following values:

    • Agent ID: ig_agent

    • Password: password

    Leave all other values as default.

    For AM 6.5.x and earlier versions, set up an agent as described in "Set Up an IG Agent in AM 6.5 and Earlier".

    1. Select Applications > Agents > Java (or J2EE).

    2. Add an agent with the following values:

      • Agent ID: ig_agent

      • Agent URL: http://openig.example.com:8080/agentapp

      • Server URL: http://openam.example.com:8088/openam

      • Password: password

    3. On the Global tab, deselect Agent Configuration Change Notification.

      This option stops IG from being notified about agent configuration changes in AM, because they are not required by IG.

  2. Set an environment variable for the IG agent password, and then restart IG:

    $ export AGENT_SECRET_ID='cGFzc3dvcmQ='

    The password is retrieved by a SystemAndEnvSecretStore, and must be base64-encoded.

  3. Add the following route to IG, to serve .css and other static resources for the sample application:

    $HOME/.openig/config/routes/static-resources.json
    %appdata%\OpenIG\config\routes\static-resources.json
    {
      "name" : "sampleapp_resources",
      "baseURI" : "http://app.example.com:8081",
      "condition": "${matches(request.uri.path,'^/css')}",
      "handler": "ReverseProxyHandler"
    }
  4. Add the following route to IG:

    $HOME/.openig/config/routes/jwt-builder.json
    %appdata%\OpenIG\config\routes\static-resources.json
    {
      "name": "jwt",
      "heap": [
        {
          "name": "SystemAndEnvSecretStore-1",
          "type": "SystemAndEnvSecretStore"
        },
        {
          "name": "AmService-1",
          "type": "AmService",
          "config": {
            "agent": {
              "username": "ig_agent",
              "passwordSecretId": "agent.secret.id"
            },
            "secretsProvider": "SystemAndEnvSecretStore-1",
            "url": "http://openam.example.com:8088/openam",
            "version": "7"
          }
        }
      ],
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [{
            "name": "SingleSignOnFilter",
            "type": "SingleSignOnFilter",
            "config": {
              "amService": "AmService-1"
            }
          }, {
            "name": "UserProfileFilter",
            "type": "UserProfileFilter",
            "config": {
              "username": "${contexts.ssoToken.info.uid}",
              "userProfileService": {
                "type": "UserProfileService",
                "config": {
                  "amService": "AmService-1"
                }
              }
            }
          }, {
            "name": "JwtBuilderFilter-1",
            "type": "JwtBuilderFilter",
            "config": {
              "template": {
                "name": "${contexts.userProfile.commonName}",
                "email": "${contexts.userProfile.rawInfo.mail[0]}"
              }
            }
          }, {
            "name": "HeaderFilter-1",
            "type": "HeaderFilter",
            "config": {
              "messageType": "REQUEST",
              "add": {
                "x-openig-user": ["${contexts.jwtBuilder.value}"]
              }
            }
          }],
          "handler": "ReverseProxyHandler"
        }
      },
      "condition": "${matches(request.uri.path, '/jwt$')}",
      "baseURI": "http://app.example.com:8081"
    }

    Notice the following features of the route:

    • The route matches requests to /jwt.

    • The agent password for AmService is provided by a SystemAndEnvSecretStore in the heap.

    • If the request does not have a valid AM session cookie, the SingleSignOnFilter redirects the request to authenticate with the AM server declared in the heap.

      If the request already has a valid AM session cookie, or after the user authenticates with AM, the SingleSignOnFilter passes the request to the next filter, and stores the cookie value in an SsoTokenContext.

    • The UserProfileFilter retrieves the AM user profile data from the SsoTokenContext, and stores it in the UserProfileContext.

    • The JwtBuilderFilter takes the username and email from the UserProfileContext, and stores them in a JWT in the JwtBuilderContext.

    • The HeaderFilter retrieves the JWT from the JwtBuilderContext, and adds it to the header field x-openig-user in the request.

    • The ClientHandler passes the request to the sample app, which displays the JWT.

  5. Log out of AM, and then go to http://openig.example.com:8080/jwt.

  6. Log in to AM as user demo, password Ch4ng31t, or as another user. The sample app displays the JWT along with its header and payload, and labels it as unsigned.


Packing Data Into a JWT Signed With a Symmetric Key

This example builds on "Packing Data Into a JWT", to sign the JWT with a symmetric key, and store the key in a SystemAndEnvSecretStore.

Before you start, run the example in "Packing Data Into a JWT".

  1. Set an environment variable for the base64-encoded secret to sign the JWT:

    $ export SIGNING_KEY_SECRET_ID='cGFzc3dvcmQ='
  2. Add the following route to IG:

    $HOME/.openig/config/routes/jwt-builder-signature-symmetric.json
    %appdata%\OpenIG\config\routes\jwt-builder-signature-symmetric.json
    {
      "name": "jwt-signature-symmetric",
      "condition": "${matches(request.uri.path, '/jwt-signature-symmetric')}",
      "baseURI": "http://app.example.com:8081",
      "heap": [
        {
          "name": "AmService-1",
          "type": "AmService",
          "config": {
            "agent": {
              "username": "ig_agent",
              "passwordSecretId": "agent.secret.id"
            },
            "secretsProvider": "SystemAndEnvSecretStore-1",
            "url": "http://openam.example.com:8088/openam",
            "version": "7"
          }
        },
        {
          "name": "SecretKeyPropertyFormat-1",
          "type": "SecretKeyPropertyFormat",
          "config": {
            "format": "BASE64",
            "algorithm": "AES"
          }
        },
        {
          "name": "SystemAndEnvSecretStore-1",
          "type": "SystemAndEnvSecretStore",
          "config": {
            "mappings": [{
              "secretId": "signing.key.secret.id",
              "format": "SecretKeyPropertyFormat-1"
            }]
          }
        }
      ],
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [{
            "name": "SingleSignOnFilter-1",
            "type": "SingleSignOnFilter",
            "config": {
              "amService": "AmService-1"
            }
          }, {
            "name": "UserProfileFilter-1",
            "type": "UserProfileFilter",
            "config": {
              "username": "${contexts.ssoToken.info.uid}",
              "userProfileService": {
                "type": "UserProfileService",
                "config": {
                  "amService": "AmService-1"
                }
              }
            }
          }, {
            "name": "JwtBuilderFilter-1",
            "type": "JwtBuilderFilter",
            "config": {
              "template": {
                "name": "${contexts.userProfile.commonName}",
                "email": "${contexts.userProfile.rawInfo.mail[0]}"
              },
              "secretsProvider": "SystemAndEnvSecretStore-1",
              "signature": {
                "secretId": "signing.key.secret.id",
                "algorithm": "HS256"
              }
            }
          }, {
            "name": "HeaderFilter-1",
            "type": "HeaderFilter",
            "config": {
              "messageType": "REQUEST",
              "add": {
                "x-openig-user": ["${contexts.jwtBuilder.value}"]
              }
            }
          }],
          "handler": "ReverseProxyHandler"
        }
      }
    }

    Notice the following features of the route compared to jwt-builder.json:

    • The route matches requests to /jwt-signature-symmetric.

    • The JWT signing key is managed by the SysEnvStoreSecretStore in the heap, which refers to the SecretKeyPropertyFormat for the secret's format.

    • The JwtBuilderFilter signature property refers to the JWT signing key in the SysEnvStoreSecretStore.

  3. Log out of AM, and go to http://openig.example.com:8080/jwt-signature-symmetric.

  4. Log in to AM as user demo, password Ch4ng31t, or as another user. The sample app displays the signed JWT along with its header and payload.

  5. Enter information about the secret used to sign the JWT, and verify the signature.


Packing Data Into a JWT Signed With an Asymmetric Key

This example builds on "Packing Data Into a JWT", and signs the JWT with an asymmetric RSA key.

Before you start, run the example in "Packing Data Into a JWT".

  1. Generate a PKCS12 KeyStore that contains an RSA key:

    $ keytool \
    -genkeypair \
    -keyalg RSA \
    -keysize 1024 \
    -alias signature-key \
    -keystore /path/to/secrets/jwtbuilderkeystore.pkcs12 \
    -storepass password \
    -storetype pkcs12 \
    -dname "CN=openig.example.com,O=Example Corp,C=FR"
  2. Set an environment variable for the KeyStore storepass:

    $ export KEYSTORE_SECRET_ID='cGFzc3dvcmQ='

  3. Add the following route to IG:

    $HOME/.openig/config/routes/jwt-builder-signature-asymmetric.json
    %appdata%\OpenIG\config\routes\jwt-builder-signature-asymmetric.json
    {
      "name": "jwt-signature-asymmetric",
      "condition": "${matches(request.uri.path, '/jwt-signature-asymmetric')}",
      "baseURI": "http://app.example.com:8081",
      "heap": [
        {
          "name": "AmService-1",
          "type": "AmService",
          "config": {
            "agent": {
              "username": "ig_agent",
              "passwordSecretId": "agent.secret.id"
            },
            "secretsProvider": "SystemAndEnvSecretStore-1",
            "url": "http://openam.example.com:8088/openam",
            "version": "7"
          }
        },
        {
          "name": "SystemAndEnvSecretStore-1",
          "type": "SystemAndEnvSecretStore"
        },
        {
          "name":"KeyStoreSecretStore-1",
          "type": "KeyStoreSecretStore",
          "config": {
            "file": "/path/to/secrets/jwtbuilderkeystore.pkcs12",
            "storeType": "PKCS12",
            "storePassword": "keystore.secret.id",
            "keyEntryPassword": "keystore.secret.id",
            "secretsProvider": "SystemAndEnvSecretStore-1",
            "mappings": [{
              "secretId": "id.key.for.signing.jwt",
              "aliases": [ "signature-key" ]
            }]
          }
        }
      ],
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [{
            "name": "SingleSignOnFilter-1",
            "type": "SingleSignOnFilter",
            "config": {
              "amService": "AmService-1"
            }
          }, {
            "name": "UserProfileFilter-1",
            "type": "UserProfileFilter",
            "config": {
              "username": "${contexts.ssoToken.info.uid}",
              "userProfileService": {
                "type": "UserProfileService",
                "config": {
                  "amService": "AmService-1"
                }
              }
            }
          }, {
            "name": "JwtBuilderFilter-1",
            "type": "JwtBuilderFilter",
            "config": {
              "template": {
                "name": "${contexts.userProfile.commonName}",
                "email": "${contexts.userProfile.rawInfo.mail[0]}"
              },
              "secretsProvider": "KeyStoreSecretStore-1",
              "signature": {
                "secretId": "id.key.for.signing.jwt"
              }
            }
          }, {
            "name": "HeaderFilter-1",
            "type": "HeaderFilter",
            "config": {
              "messageType": "REQUEST",
              "add": {
                "x-openig-user": ["${contexts.jwtBuilder.value}"]
              }
            }
          }],
          "handler": "ReverseProxyHandler"
        }
      }
    }

    Notice the following features of the route compared to jwt-builder.json:

    • The route matches requests to /jwt-signature-asymmetric.

    • The JWT signing key is managed by the KeyStoreSecretStore in the heap, which maps the alias of the JWT signing key to its secret ID.

    • The password for the KeyStoreSecretStore is managed by the SystemAndEnvSecretStore in the heap.

    • The JwtBuilderFilter property signature refers to the mapping for the signing key in KeyStoreSecretStore.

  4. Log out of AM, and then go to http://openig.example.com:8080/jwt-signature-asymmetric.

  5. Log in to AM as user demo, password Ch4ng31t, or as another user. The sample app displays the signed JWT along with its header and payload.

  6. Enter information about the KeyStore, and verify the signature.


Packing Data Into a Signed and Encrypted JWT

This example builds on "Packing Data Into a JWT Signed With an Asymmetric Key" to encrypt the signed JWT by using a new key in the pkcs12 KeyStore.

Before you start, run the example in "Packing Data Into a JWT Signed With an Asymmetric Key".

  1. Generate another key in the KeyStore:

    $ keytool \
      -genkeypair \
      -keyalg RSA \
      -keysize 1024 \
      -alias encryption-key \
      -keystore /path/to/secrets/jwtbuilderkeystore.pkcs12 \
      -storepass password \
      -storetype pkcs12 \
      -dname "CN=openig.example.com,O=Example Corp,C=FR"
  2. Add the following route to IG:

    $HOME/.openig/config/routes/jwt-builder-encrypt.json
    %appdata%\OpenIG\config\routes\jwt-builder-encrypt.json
    {
      "name": "jwt-encryption",
      "condition": "${matches(request.uri.path, '/jwt-encryption$')}",
      "baseURI": "http://app.example.com:8081",
      "heap": [
        {
          "name": "SystemAndEnvSecretStore-1",
          "type": "SystemAndEnvSecretStore"
        },
        {
          "name": "AmService-1",
          "type": "AmService",
          "config": {
            "agent": {
              "username": "ig_agent",
              "passwordSecretId": "agent.secret.id"
            },
            "secretsProvider": "SystemAndEnvSecretStore-1",
            "url": "http://openam.example.com:8088/openam",
            "version": "7"
          }
        },
        {
          "name":"myKeyStoreSecretStore",
          "type": "KeyStoreSecretStore",
          "config": {
            "file": "/path/to/secrets/jwtbuilderkeystore.pkcs12",
            "storeType": "PKCS12",
            "storePassword": "keystore.secret.id",
            "keyEntryPassword": "keystore.secret.id",
            "secretsProvider": "SystemAndEnvSecretStore-1",
            "mappings": [
              {
                "secretId": "id.key.for.signing.jwt",
                "aliases": [ "signature-key" ]
              },
              {
                "secretId": "id.key.for.encrypting.jwt",
                "aliases": [ "encryption-key" ]
              }
            ]
          }
        }
      ],
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [{
            "name": "SingleSignOnFilter-1",
            "type": "SingleSignOnFilter",
            "config": {
              "amService": "AmService-1"
            }
          }, {
            "name": "UserProfileFilter-1",
            "type": "UserProfileFilter",
            "config": {
              "username": "${contexts.ssoToken.info.uid}",
              "userProfileService": {
                "type": "UserProfileService",
                "config": {
                  "amService": "AmService-1"
                }
              }
            }
          }, {
            "name": "JwtBuilderFilter-1",
            "type": "JwtBuilderFilter",
            "config": {
              "template": {
                "name": "${contexts.userProfile.commonName}",
                "email": "${contexts.userProfile.rawInfo.mail[0]}"
              },
              "secretsProvider": "myKeyStoreSecretStore",
              "signature": {
                "secretId": "id.key.for.signing.jwt",
                "encryption": {
                  "secretId": "id.key.for.encrypting.jwt",
                  "algorithm": "RSA-OAEP-256",
                  "method": "A128CBC-HS256"
                }
              }
            }
          }, {
            "name": "HeaderFilter-1",
            "type": "HeaderFilter",
            "config": {
              "messageType": "REQUEST",
              "add": {
                "x-openig-user": ["${contexts.jwtBuilder.value}"]
              }
            }
          }],
          "handler": "ReverseProxyHandler"
        }
      }
    }

    Notice the following features of the route compared to jwt-builder-signature-asymmetric.json:

    • The route matches requests to /jwt-encryption.

    • The KeyStoreSecretStore includes a secret ID mapping to the alias of the encryption key.

    • The JwtBuilderFilter adds a signature/encryption property to refer to the mapping for the encryption key in KeyStoreSecretStore.

  3. Go to http://openig.example.com:8080/jwt-encryption.

  4. Log in to AM as user demo, password Ch4ng31t, or as another user. The sample app displays the signed JWT along with its header and encryption info. Because the JWT is encrypted, the sample app doesn't display the payload.

  5. Enter information about the KeyStore, and select Decrypt JWT to display the payload.


Read a different version of :