JwtValidationFilter

Validates an unsigned, signed, encrypted, or signed and encrypted JWT. The order of signing and encryption is not important; a JWT can be signed and then encrypted, or encrypted and then signed.

If the JWT is validated, the request continues down the chain. The data is provided in the "JwtValidationContext".

If the JWT is not validated, data is provided in the "JwtValidationErrorContext". If a failure handler is configured, the request passes to the failure handler. Otherwise, an HTTP 403 Forbidden is returned.

Usage

{
  "name": string,
  "type": "JwtValidationFilter",
  "config": {
    "jwt": runtime expression<string>,
    "verificationSecretId": configuration expression<secret-id>,
    "decryptionSecretId": configuration expression<secret-id>,
    "secretsProvider": SecretsProvider reference,
    "skewAllowance": configuration expression<duration>,
    "customizer": JwtValidatorCustomizer reference,
    "failureHandler": handler reference
  }
}

Properties

"jwt": runtime expression<string>, required

The value of the JWT in the request. Cannot be null.

"verificationSecretId": configuration expression<secret-id>, required to verify the signature of signed tokens

The secret ID for the secret to verify the signature of signed tokens.

If configured, the token must be signed. If not configured, IG does not verify the signature.

For information about how signatures are validated, see "Validating the Signature of Signed Tokens". For information about how each type of secret store resolves named secrets, see Secret Stores.

"decryptionSecretId": configuration expression<secret-id>, required if AM secures access_tokens with encryption

The secret ID for the secret to verify the encryption of tokens.

If configured, the token must be encrypted. If not configured, IG does not verify the encryption.

For information about how each type of secret store resolves named secrets, see Secret Stores.

"secretsProvider": SecretsProvider reference, required to verify the signature of signed JWTs

The "SecretsProvider" to use to resolve queried secrets, such as passwords and cryptographic keys. Provide either the name of a SecretsProvider object defined in the heap, or specify a SecretsProvider object inline.

"customizer": JwtValidatorCustomizer reference, optional

Defines a set of validation constraints for JWT claims and sub-claims. If a claim is not validated against the constraint, the JWT is not validated.

JwtValidatorCustomizer provides a ScriptableJwtValidatorCustomizer to enrich a builder object by using its methods. Get more information about the following items:

The following example checks that the value of the claim sub is george:

"customizer": {
  "type": "ScriptableJwtValidatorCustomizer",
  "config": {
    "type": "application/x-groovy",
    "source": [ "builder.claim('sub', JsonValue::asString, isEqualTo('george'))" ]
  }
}

The following example checks that the value of the custom sub-claim is ForgeRock:

"customizer": {
  "type": "ScriptableJwtValidatorCustomizer",
  "config": {
    "type": "application/x-groovy",
    "source": [
      builder.claim('customclaim/subclaim', JsonValue::asString, isEqualTo('ForgeRock'));
    ]
  }
}

The following example checks the value of multiple claims:

"customizer": {
  "type": "ScriptableJwtValidatorCustomizer",
  "config": {
    "type": "application/x-groovy",
    "source": [
      "builder.claim('aud', listOf(JsonValue::asString), contains('My App'))",
      "       .claim('iat', instant(), isInThePast())",
      "       .claim('exp', instant(), isInTheFuture());",
      "builder.claim('iss', JsonValue::asString, isEqualTo('ForgeRock AM'));"
    ]
  }
}

Default: Claims are not validated

"skewAllowance": configuration expression<duration>, optional

The duration to add to the validity period of a JWT to allow for clock skew between different servers. To support a zero-trust policy, the skew allowance is by default zero.

A skewAllowance of 2 minutes affects the validity period as follows:

  • A JWT with an iat of 12:00 is valid from 11:58 on the IG clock.

  • A JWT with an exp 13:00 is expired after 13:02 on the IG clock.

Default: zero

"failureHandler": handler reference, optional

Handler to treat the request on failure.

Provide an inline handler configuration object, or the name of a handler object declared in the heap. See also Handlers.

Default: HTTP 403 Forbidden, the request stops being executed.

Example

Validate an Unsigned, Unencrypted JWT
  1. Set up AM as described in "Validating Access_Tokens Through the Introspection Endpoint", and add the additional scope openid to the OAuth 2.0 Client.

  2. In a terminal window, use a curl command similar to the following to retrieve a JWT:

    $ curl -s \
    --user "client-application:password" \
    --data "grant_type=password&username=demo&password=Ch4ng31t&scope=openid" \
    http://openam.example.com:8088/openam/oauth2/access_token | jq .
    {
      "access_token":"...",
      "scope":"openid",
      "id_token":"...",
      "token_type":"Bearer",
      "expires_in":3599
    }
  3. Add the following route to IG, and replace <jwt_value> with the value of the id_token returned in the previous step:

    $HOME/.openig/config/routes/jwtvalidation.json
    %appdata%\OpenIG\config\routes\jwtvalidation.json
    {
      "name": "jwtvalidation",
      "condition": "${matches(request.uri.path, '^/jwtvalidation')}",
      "capture": "all",
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [{
            "type": "JwtValidationFilter",
            "config": {
              "jwt": "<jwt_value>",
              "failureHandler": {
                "type": "ScriptableHandler",
                "config": {
                  "type": "application/x-groovy",
                  "source": [
                    "def response = new Response(Status.FORBIDDEN)",
                    "response.headers['Content-Type'] = 'text/html; charset=utf-8'",
                    "def errors = contexts.jwtValidationError.violations.collect{it.description}",
                    "def display = \"<html>Can't validate JWT:<br> ${contexts.jwtValidationError.jwt} \"",
                    "display <<=\"<br><br>For the following errors:<br> ${errors.join(\"<br>\")}</html>\"",
                    "response.entity=display as String",
                    "return response"
                  ]
                }
              },
              "verificationSecretId": "verify",
              "secretsProvider": {
                "type": "JwkSetSecretStore",
                "config": {
                  "jwkUrl": "http://openam.example.com:8088/openam/oauth2/connect/jwk_uri"
                }
              }
            }
          }],
          "handler": {
            "type": "StaticResponseHandler",
            "config": {
              "status": 200,
              "headers": {
                "Content-Type": [ "text/html" ]
              },
              "entity": "<html><body>Validated JWT:<br> ${contexts.jwtValidation.value}</body></html>"
            }
          }
        }
      }
    }
    

    Notice the following features of the route:

    • The route matches requests to /jwtvalidation.

    • The property secretsProvider declares a JwkSetSecretStore to validate secrets for signed JWTs, specifying the URL to a JWK set on AM that contains the signing keys.

    • If the filter validates the token, the StaticResponseHandler displays the JWT value from the context ${contexts.jwtValidation.value}. Otherwise, the ScriptableHandler displays the JWT value and a list of violations from the context ${contexts.jwtValidationError.violations}

  4. Test the setup:

    1. Access the route on http://openig.example.com:8080/jwtvalidation.

      The JWT is displayed.

    2. In the route, invalidate the JWT by changing its value, and then access the route again.

      The value of the JWT, and the reasons that the JWT is invalid, are displayed.

Validate an Encrypted JWT
  1. Set up keys and AM as described in "Validating Encrypted Access_Tokens With the StatelessAccessTokenResolver and KeyStoreSecretStore".

  2. In a terminal window, use a curl command similar to the following to retrieve a JWT:

    $ 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 .
    
    {
     "access_token": "eyJ...pvw",
     "scope": "myscope",
     "token_type": "Bearer",
     "expires_in": 3598
     }
  3. Add the following route to IG, and replace <jwt_value> with the value of the access_token returned in the previous step, and ig_keystore_directory with your directory:

    $HOME/.openig/config/routes/jwtvalidation-encrypted
    %appdata%\OpenIG\config\routes\jwtvalidation-encrypted
    {
      "name": "jwtvalidation-encrypted",
      "condition": "${matches(request.uri.path, '^/jwtvalidation-encrypted')}",
      "capture": "all",
      "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",
        "config": {
          "filters": [{
            "type": "JwtValidationFilter",
            "config": {
              "jwt": "<jwt_value>",
              "failureHandler": {
                "type": "ScriptableHandler",
                "config": {
                  "type": "application/x-groovy",
                  "source": [
                    "def response = new Response(Status.FORBIDDEN)",
                    "response.headers['Content-Type'] = 'text/html; charset=utf-8'",
                    "def errors = contexts.jwtValidationError.violations.collect{it.description}",
                    "def display = \"<html>Can't validate JWT:<br> ${contexts.jwtValidationError.jwt} \"",
                    "display <<=\"<br><br>For the following errors:<br> ${errors.join(\"<br>\")}</html>\"",
                    "response.entity=display as String",
                    "return response"
                  ]
                }
              },
              "secretsProvider": "KeyStoreSecretStore-1",
              "decryptionSecretId": "stateless.access.token.decryption.key"
            }
          }],
          "handler": {
            "type": "StaticResponseHandler",
            "config": {
              "status": 200,
              "headers": {
                "Content-Type": [ "text/html" ]
              },
              "entity": "<html>Validated JWT:<br> ${contexts.jwtValidation.value}</html>"
            }
          }
        }
      }
    }
    

    Notice the following features of the route compared to jwtvalidation.json:

    • The route matches requests to /jwtvalidation-encrypted.

    • The JwtValidationFilter uses the KeyStoreSecretStore in the heap to provide secrets for verification and decryption of the access_token. The secrets IDs are mapped in the KeyStoreSecretStore.

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

  4. Test the setup:

    1. Access the route on http://openig.example.com:8080/jwtvalidation-encrypted.

      The JWT is displayed.

    2. In the route, invalidate the JWT by changing its value, and then access the route again.

      The value of the JWT, and the reasons that the JWT is invalid, are displayed.

Read a different version of :