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>, requiredA 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, optionalThe 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, optionalA 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 ifsignature
is usedThe secret ID of the key used to sign the JWT.
For information about supported formats for
secret-id
, see secret-id."algorithm"
: expression<string>, optionalThe algorithm with which to sign the JWT.
The following algorithms are supported but not necessarily tested in IG:
Algorithms described in Cryptographic Algorithms for Digital Signatures and MACs .
For RSASSA-PSS, you must install Bouncy Castle. For information, see The Legion of the Bouncy Castle .
From IG 6.1,
Ed25519
described in CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures .
Default: RS256
"encryption"
: object, optionalEncrypt the JWT.
{ "encryption": { "secretId": configuration expression<secret-id>, "algorithm": configuration expression<string>, "method": configuration expression<enumeration> } }
"secretId"
: configuration expression<secret-id>, optionalThe 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>, requiredThe algorithm used to encrypt the JWT.
For information about available algorithms, see "alg" (Algorithm) Header Parameter Values for JWE .
"method"
: configuration expression<enumeration>, requiredThe method used to encrypt the JWT.
For information about available methods, see "enc" (Encryption Algorithm) Header Parameter Values for JWE .
Examples
In this example, the JwtBuilderFilter takes the username and email from the UserProfileContext, and stores them in an unsigned, unencrypted JWT.
Before you start:
Prepare IG and the sample app as described in Getting Started Guide
Install and configure AM on http://openam.example.com:8088/openam, using the default configuration.
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".
Select Applications > Agents > Java (or J2EE).
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
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.
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.
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" }
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.
Log out of AM, and then go to http://openig.example.com:8080/jwt.
Log in to AM as user
demo
, passwordCh4ng31t
, or as another user. The sample app displays the JWT along with its header and payload, and labels it as unsigned.
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".
Set an environment variable for the base64-encoded secret to sign the JWT:
$
export SIGNING_KEY_SECRET_ID='cGFzc3dvcmQ='
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.
Log out of AM, and go to http://openig.example.com:8080/jwt-signature-symmetric.
Log in to AM as user
demo
, passwordCh4ng31t
, or as another user. The sample app displays the signed JWT along with its header and payload.Enter information about the secret used to sign the JWT, and verify the signature.
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".
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"
Set an environment variable for the KeyStore
storepass
:$
export KEYSTORE_SECRET_ID='cGFzc3dvcmQ='
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.
Log out of AM, and then go to http://openig.example.com:8080/jwt-signature-asymmetric.
Log in to AM as user
demo
, passwordCh4ng31t
, or as another user. The sample app displays the signed JWT along with its header and payload.Enter information about the KeyStore, and verify the signature.
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".
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"
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.
Log in to AM as user
demo
, passwordCh4ng31t
, 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.Enter information about the KeyStore, and select
Decrypt JWT
to display the payload.