Identity Gateway 7.2

Pass runtime data downstream in a JWT

The following sections describe how to pass identity or other runtime information in a JWT, downstream to a protected application:

The examples in this section use the following objects:

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

Pass runtime data in a JWT signed with a PEM

  1. Set up secrets

    1. Locate a directory for secrets, and go to it:

      $ cd /path/to/secrets
    2. Generate PEM files to sign and verify the JWT:

      $ openssl req \
      -newkey rsa:2048 \
      -new \
      -nodes \
      -x509 \
      -days 3650 \
      -subj "/CN=ig.example.com/OU=example/O=com/L=fr/ST=fr/C=fr" \
      -keyout id.key.for.signing.jwt.pem \
      -out id.key.for.verifying.jwt.pem
  2. Set up AM:

    1. (From AM 6.5.3) Select Services > Add a Service, and add a Validation Service with the following Valid goto URL Resources:

      • http://ig.example.com:8080/*

      • http://ig.example.com:8080/*?*

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

  3. Set up IG:

    1. 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.

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

      • Linux

      • Windows

      $HOME/.openig/config/routes/static-resources.json
      %appdata%\OpenIG\config\routes\static-resources.json
      {
        "name" : "sampleapp-resources",
        "baseURI" : "http://app.example.com:8081",
        "condition": "${find(request.uri.path,'^/css')}",
        "handler": "ReverseProxyHandler"
      }
    3. Add the following route to IG, replacing value of the property secretsDir with the directory for the PEM file:

      • Linux

      • Windows

      $HOME/.openig/config/routes/jwt-builder-sign-pem.json
      %appdata%\OpenIG\config\routes\jwt-builder-sign-pem.json
      {
        "name": "jwt-builder-sign-pem",
        "condition": "${find(request.uri.path, '/jwt-builder-sign-pem')}",
        "baseURI": "http://app.example.com:8081",
        "properties": {
          "secretsDir": "/path/to/secrets"
        },
        "capture": "all",
        "heap": [
          {
            "name": "pemPropertyFormat",
            "type": "PemPropertyFormat"
          },
          {
            "name": "FileSystemSecretStore-1",
            "type": "FileSystemSecretStore",
            "config": {
              "format": "PLAIN",
              "directory": "&{secretsDir}",
              "suffix": ".pem",
              "mappings": [{
                "secretId": "id.key.for.signing.jwt",
                "format": "pemPropertyFormat"
              }]
            }
          },
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          },
          {
            "name": "AmService-1",
            "type": "AmService",
            "config": {
              "agent": {
                "username": "ig_agent",
                "passwordSecretId": "agent.secret.id"
              },
              "secretsProvider": "SystemAndEnvSecretStore-1",
              "url": "http://am.example.com:8088/openam",
              "version": "7.2"
            }
          }
        ],
        "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]}"
                },
                "secretsProvider": "FileSystemSecretStore-1",
                "signature": {
                  "secretId": "id.key.for.signing.jwt",
                  "algorithm": "RS512"
                }
              }
            }, {
              "name": "HeaderFilter-1",
              "type": "HeaderFilter",
              "config": {
                "messageType": "REQUEST",
                "add": {
                  "x-openig-user": ["${contexts.jwtBuilder.value}"]
                }
              }
            }],
            "handler": "ReverseProxyHandler"
          }
        }
      }

      Notice the following features of the route:

      • The route matches requests to /jwt-builder-sign-pem.

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

      • If the request does not have a valid AM session cookie, the SingleSignOnFilter redirects the request to authenticate with AM. If the request already has a valid AM session cookie, the SingleSignOnFilter passes the request to the next filter, and stores the cookie value in an SsoTokenContext.

      • The UserProfileFilter reads the username from the SsoTokenContext, uses it to retrieve the user’s profile info from AM, and places the data into the UserProfileContext.

      • The JwtBuilderFilter refers to the secret ID of the PEM, and uses the FileSystemSecretStore to manage the secret.

      • The FileSystemSecretStore mapping refers to the secret ID of the PEM, and uses the PemPropertyFormat to define the format.

      • The HeaderFilter retrieves the JWT from the JwtBuilderContext, and adds it to the header field x-openig-user in the request, so that the sample app can display the JWT.

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

  4. Test the setup

    1. If you are logged in to AM, log out and clear any cookies.

    2. Go to http://ig.example.com:8080/jwt-builder-sign-pem.

    3. 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.

    4. In USE PEM FILE in the sample app, enter the path to id.key.for.verifying.jwt.pem to verify the JWT signature.

Pass runtime data in a JWT signed with PEM then encrypted with a symmetric key

This example passes runtime data in a JWT that is signed with a PEM, and then encrypted with a symmetric key.

  1. Set up secrets

    1. Locate a directory for secrets, and go to it:

      $ cd /path/to/secrets
    2. From the secrets directory, generate PEM files to sign and verify the JWT:

      $ openssl req \
      -newkey rsa:2048 \
      -new \
      -nodes \
      -x509 \
      -days 3650 \
      -subj "/CN=ig.example.com/OU=example/O=com/L=fr/ST=fr/C=fr" \
      -keyout id.key.for.signing.jwt.pem \
      -out id.key.for.verifying.jwt.pem
    3. Encrypt the PEM file used to sign the JWT:

      $ openssl pkcs8 \
      -topk8 \
      -inform PEM \
      -outform PEM \
      -in id.key.for.signing.jwt.pem \
      -out id.encrypted.key.for.signing.jwt.pem \
      -passout pass:encryptedpassword \
      -v1 PBE-SHA1-3DES

      The encrypted PEM file used for signatures is id.encrypted.key.for.signing.jwt.pem. The password to decode the file is encryptedpassword.

      If encryption fails, make sure that your encryption methods and ciphers are supported by the Java Cryptography Extension.
    4. Generate a symmetric key to encrypt the JWT:

      $ openssl rand -base64 32 > symmetric.key.for.encrypting.jwt
    5. Make sure that you have the following keys in your secrets directory:

      • id.encrypted.key.for.signing.jwt.pem

      • id.key.for.signing.jwt.pem

      • id.key.for.verifying.jwt.pem

      • symmetric.key.for.encrypting.jwt

  2. Set up AM:

    1. (From AM 6.5.3) Select Services > Add a Service, and add a Validation Service with the following Valid goto URL Resources:

      • http://ig.example.com:8080/*

      • http://ig.example.com:8080/*?*

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

  3. Set up IG:

    1. In IG, create an environment variable for the base64-encoded password to decrypt the PEM file used to sign the JWT:

      $ export ID_DECRYPTED_KEY_FOR_SIGNING_JWT='ZW5jcnlwdGVkcGFzc3dvcmQ='
    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:

      • Linux

      • Windows

      $HOME/.openig/config/routes/static-resources.json
      %appdata%\OpenIG\config\routes\static-resources.json
      {
        "name" : "sampleapp-resources",
        "baseURI" : "http://app.example.com:8081",
        "condition": "${find(request.uri.path,'^/css')}",
        "handler": "ReverseProxyHandler"
      }
    4. Add the following route to IG, replacing the value of secretsDir with your secrets directory:

      • Linux

      • Windows

      $HOME/.openig/config/routes/jwtbuilder-sign-then-encrypt.json
      %appdata%\OpenIG\config\routes\jwtbuilder-sign-then-encrypt.json
      {
        "name": "jwtbuilder-sign-then-encrypt",
        "condition": "${find(request.uri.path, '/jwtbuilder-sign-then-encrypt')}",
        "baseURI": "http://app.example.com:8081",
        "properties": {
          "secretsDir": "/path/to/secrets"
        },
        "capture": "all",
        "heap": [
          {
            "name": "SystemAndEnvSecretStore",
            "type": "SystemAndEnvSecretStore",
            "config": {
              "mappings": [{
                "secretId": "id.decrypted.key.for.signing.jwt",
                "format": "BASE64"
              }]
            }
          },
          {
            "name": "AmService-1",
            "type": "AmService",
            "config": {
              "agent": {
                "username": "ig_agent",
                "passwordSecretId": "agent.secret.id"
              },
              "secretsProvider": "SystemAndEnvSecretStore",
              "url": "http://am.example.com:8088/openam",
              "version": "7.2"
            }
          },
          {
            "name": "pemPropertyFormat",
            "type": "PemPropertyFormat",
            "config": {
              "decryptionSecretId": "id.decrypted.key.for.signing.jwt",
              "secretsProvider": "SystemAndEnvSecretStore"
            }
          },
          {
            "name": "FileSystemSecretStore-1",
            "type": "FileSystemSecretStore",
            "config": {
              "format": "PLAIN",
              "directory": "&{secretsDir}",
              "mappings": [{
                "secretId": "id.encrypted.key.for.signing.jwt.pem",
                "format": "pemPropertyFormat"
              }, {
                "secretId": "symmetric.key.for.encrypting.jwt",
                "format": {
                  "type": "SecretKeyPropertyFormat",
                  "config": {
                    "format": "BASE64",
                    "algorithm": "AES"
                  }
                }
              }]
            }
          }
        ],
        "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]}"
                },
                "secretsProvider": "FileSystemSecretStore-1",
                "signature": {
                  "secretId": "id.encrypted.key.for.signing.jwt.pem",
                  "algorithm": "RS512",
                  "encryption": {
                    "secretId": "symmetric.key.for.encrypting.jwt",
                    "algorithm": "dir",
                    "method": "A128CBC-HS256"
                  }
                }
              }
            }, {
              "name": "AddBuiltJwtToHeader",
              "type": "HeaderFilter",
              "config": {
                "messageType": "REQUEST",
                "add": {
                  "x-openig-user": ["${contexts.jwtBuilder.value}"]
                }
              }
            },
              {
                "name": "AddBuiltJwtAsCookie",
                "type": "HeaderFilter",
                "config": {
                  "messageType": "RESPONSE",
                  "add": {
                    "set-cookie": ["my-jwt=${contexts.jwtBuilder.value};PATH=/"]
                  }
                }
              }],
            "handler": "ReverseProxyHandler"
          }
        }
      }

      Notice the following features of the route:

      • The route matches requests to /jwtbuilder-sign-then-encrypt.

      • The SystemAndEnvSecretStore provides the IG agent password and the password to decode the PEM file for the signing keys.

      • The FileSystemSecretStore maps the secret IDs of the encrypted PEM file used to sign the JWT, and the symmetric key used to encrypt the JWT.

      • After authentication, the UserProfileFilter reads the username from the SsoTokenContext, uses it to retrieve the user’s profile info from AM, and places the data into the UserProfileContext.

      • The JwtBuilderFilter takes the username and email from the UserProfileContext, and stores them in a JWT in the JwtBuilderContext. It uses the secrets mapped in the FileSystemSecretStore to sign then encrypt the JWT.

      • The AddBuiltJwtToHeader HeaderFilter retrieves the JWT from the JwtBuilderContext, and adds it to the header field x-openig-user in the request so that the sample app can display the JWT.

      • The AddBuiltJwtAsCookie HeaderFilter adds the JWT to a cookie called my-jwt so that it can be retrieved by the JwtValidationFilter in Validate JWTs. The cookie is ignored in this example.

      • The ClientHandler passes the request to the sample app.

  4. Test the setup

    1. If you are logged in to AM, log out and clear any cookies.

    2. Go to http://ig.example.com:8080/jwtbuilder-sign-then-encrypt.

    3. Log in to AM as user demo, password Ch4ng31t. The sample app displays the encrypted JWT. The payload is concealed because the JWT is encrypted.

    4. In the ENTER SECRET box, enter the value of symmetric.key.for.encrypting.jwt to decrypt the JWT. The signed JWT and its payload are now displayed.

    5. In the USE PEM FILE box, enter the path to id.key.for.verifying.jwt.pem to verify the JWT signature.

Pass runtime data in JWT encrypted with a symmetric key

  1. Set up secrets:

    1. Locate a directory for secrets, and go to it:

      $ cd /path/to/secrets
    2. In the secrets folder, generate an AES 256-bit key:

      $ openssl rand -base64 32
      loH...UFQ=
    3. In the secrets folder, create a file called symmetric.key.for.encrypting.jwt containing the AES key:

      $ echo -n 'loH...UFQ=' > symmetric.key.for.encrypting.jwt

      Make sure that the password file contains only the password, with no trailing spaces or carriage returns.

  2. Set up AM:

    1. (From AM 6.5.3) Select Services > Add a Service, and add a Validation Service with the following Valid goto URL Resources:

      • http://ig.example.com:8080/*

      • http://ig.example.com:8080/*?*

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

  3. Set up IG:

    1. 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.

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

      • Linux

      • Windows

      $HOME/.openig/config/routes/static-resources.json
      %appdata%\OpenIG\config\routes\static-resources.json
      {
        "name" : "sampleapp-resources",
        "baseURI" : "http://app.example.com:8081",
        "condition": "${find(request.uri.path,'^/css')}",
        "handler": "ReverseProxyHandler"
      }
    3. Add the following route to IG, replacing the value of the property secretsDir with your value:

      • Linux

      • Windows

      $HOME/.openig/config/routes/jwtbuilder-encrypt-symmetric.json
      %appdata%\OpenIG\config\routes\jwtbuilder-encrypt-symmetric.json
      {
        "name": "jwtbuilder-encrypt-symmetric",
        "condition": "${find(request.uri.path, '/jwtbuilder-encrypt-symmetric')}",
        "baseURI": "http://app.example.com:8081",
        "properties": {
          "secretsDir": "/path/to/secrets"
        },
        "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://am.example.com:8088/openam",
              "version": "7.2"
            }
          },
          {
            "name": "FileSystemSecretStore-1",
            "type": "FileSystemSecretStore",
            "config": {
              "format": "PLAIN",
              "directory": "&{secretsDir}",
              "mappings": [{
                "secretId": "symmetric.key.for.encrypting.jwt",
                "format": {
                  "type": "SecretKeyPropertyFormat",
                  "config": {
                    "format": "BASE64",
                    "algorithm": "AES"
                  }
                }
              }]
            }
          }
        ],
        "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": "FileSystemSecretStore-1",
                "encryption": {
                  "secretId": "symmetric.key.for.encrypting.jwt",
                  "algorithm": "dir",
                  "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:

      • The route matches requests to /jwtbuilder-encrypt-symmetric.

      • The JWT encryption key is managed by the FileSystemSecretStore in the heap, which defines the SecretKeyPropertyFormat.

      • The JwtBuilderFilter encryption property refers to key in the FileSystemSecretStore.

      • The HeaderFilter retrieves the JWT from the JwtBuilderContext, and adds it to the header field x-openig-user in the request, so that the sample app can display the JWT.

  4. Test the setup:

    1. If you are logged in to AM, log out and clear any cookies.

    2. Go to http://ig.example.com:8080/jwtbuilder-encrypt-symmetric.

    3. Log in to AM as user demo, password Ch4ng31t, or as another user. The JWT is displayed in the sample app.

    4. In the ENTER SECRET field, enter the value of the AES 256-bit key to decrypt the JWT and display its payload.

Pass runtime data in JWT encrypted with an asymmetric key

The asymmetric key in this example is a PEM, but you can equally use a keystore.

  1. Set up secrets:

    1. Locate a directory for secrets, and go to it:

      $ cd /path/to/secrets
    2. Generate an encrypted PEM file:

      $ openssl req \
      -newkey rsa:2048 \
      -new \
      -nodes \
      -x509 \
      -days 3650 \
      -subj "/CN=ig.example.com/OU=example/O=com/L=fr/ST=fr/C=fr" \
      -keyout id.key.for.encrypting.jwt.pem \
      -out id.key.for.decrypting.jwt.pem
  2. Set up AM:

    1. (From AM 6.5.3) Select Services > Add a Service, and add a Validation Service with the following Valid goto URL Resources:

      • http://ig.example.com:8080/*

      • http://ig.example.com:8080/*?*

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

  3. Set up IG:

    1. 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.

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

      • Linux

      • Windows

      $HOME/.openig/config/routes/static-resources.json
      %appdata%\OpenIG\config\routes\static-resources.json
      {
        "name" : "sampleapp-resources",
        "baseURI" : "http://app.example.com:8081",
        "condition": "${find(request.uri.path,'^/css')}",
        "handler": "ReverseProxyHandler"
      }
    3. Add the following route to IG, replacing value of the property secretsDir with the directory for the PEM file:

      • Linux

      • Windows

      $HOME/.openig/config/routes/jwtbuilder-encrypt-asymmetric.json
      %appdata%\OpenIG\config\routes\jwtbuilder-encrypt-asymmetric.json
      {
        "name": "jwtbuilder-encrypt-asymmetric",
        "condition": "${find(request.uri.path, '/jwtbuilder-encrypt-asymmetric')}",
        "baseURI": "http://app.example.com:8081",
        "properties": {
          "secretsDir": "/path/to/secrets"
        },
        "capture": "all",
        "heap": [
          {
            "name": "pemPropertyFormat",
            "type": "PemPropertyFormat"
          },
          {
            "name": "FileSystemSecretStore-1",
            "type": "FileSystemSecretStore",
            "config": {
              "format": "PLAIN",
              "directory": "&{secretsDir}",
              "suffix": ".pem",
              "mappings": [{
                "secretId": "id.key.for.decrypting.jwt",
                "format": "pemPropertyFormat"
              }]
            }
          },
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          },
          {
            "name": "AmService-1",
            "type": "AmService",
            "config": {
              "agent": {
                "username": "ig_agent",
                "passwordSecretId": "agent.secret.id"
              },
              "secretsProvider": "SystemAndEnvSecretStore-1",
              "url": "http://am.example.com:8088/openam",
              "version": "7.2"
            }
          }
        ],
        "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]}"
                },
                "secretsProvider": "FileSystemSecretStore-1",
                "encryption": {
                  "secretId": "id.key.for.decrypting.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:

      • The route matches requests to /jwtbuilder-encrypt-asymmetric.

      • The JwtBuilderFilter refers to the secret ID of the PEM, and uses the FileSystemSecretStore to manage the secret.

      • The FileSystemSecretStore mapping refers to the secret ID of the PEM, and uses the default PemPropertyFormat.

      • The HeaderFilter retrieves the JWT from the JwtBuilderContext, and adds it to the header field x-openig-user in the request, so that the sample app can display the JWT.

  4. Test the setup:

    1. If you are logged in to AM, log out and clear any cookies.

    2. Go to http://ig.example.com:8080/jwtbuilder-encrypt-asymmetric.

    3. Log in to AM as user demo, password Ch4ng31t, or as another user. The JWT is displayed in the sample app.

    4. In the USE PEM FILE field, enter the path to id.key.for.encrypting.jwt.pem to decrypt the JWT and display its payload.