---
title: JWT validation with PingAM
description: The following examples show how to use the JwtValidationFilter to validate signed and encrypted JWT.
component: pinggateway
version: 2026
page_id: pinggateway:gateway-guide:validate-jwt
canonical_url: https://docs.pingidentity.com/pinggateway/2026/gateway-guide/validate-jwt.html
revdate: 2025-10-15T18:45:22Z
keywords: ["Configuration", "JSON"]
---

# JWT validation with PingAM

The following examples show how to use the [JwtValidationFilter](../reference/JwtValidationFilter.html) to validate signed and encrypted JWT.

The JwtValidationFilter can access JWTs in the request, provided in a header, query parameter, form parameter, cookie, or other way. If an upstream filter makes the JWT available in the request's attributes context, the JwtValidationFilter can access the JWT through the context, for example, at `${attributes.jwtToValidate}`.

For convenience, the JWT in this example is provided by the JwtBuilderFilter, and passed to the JwtValidationFilter in a cookie.

The following figure shows the flow of information in the example:

![jwtvalidation](https://kroki.io/plantuml/svg/eNqFU01v2zAMvetXELn0lHbrYQMCJIBbIM0CDA3mpLvkIttMTESRPElOmv2j_Y39slGy0jntPnyxLb73SD5S4uZKwL1pTpa2tYefP-D23fuPMOTX7QdYkN7Cpwq1J39imG2MlZ6MFgKWNTkoTYXAb2-gQGgdVoDPpWodHVCdgDQjtMYycOBIvv6HJDiz8UdpEYwFh_ZAJbpr8XcGGM05zGaD1oFry_qPCqE2hVupICoQOgHH2kAtDxiO0HLRpBkloSBdhWyKidohyK1F3DPoP7Vfi6sbIWTrjW73BVrRSOuppEYydQDxuVcUhNKz1lnTcJpIH4B0KX7JzFpfG0vfu3Zz7gjtWocyss-RlOWiMM8wCEcP0uNRngaXEjlHFOa01Y96SorbjcQ8f5xeAlds2MKaDSns4VaLV7D50d-1pCq0PdT87hVqhvISMXsr8yQVVbGzvtLTVKCugLsS4zEEA4Ld7BMCjMeic2k4yfJRPxjssfitRedFlg8nHWwU2uT575AXltVi4TD_uuwrBSdG8KUjd3P-zQrBM-TByqI7ho01-3O-ta5jswkbCjuLdWDSG2P33Y6H2jq15ZtYP1fyBsHX8kXHgeSLdMCE5NGEVN4SHgIw3j9ej6YbIkjPoaL1vPAMHU54SCmt4M_0f28079Q5BV8Cx5uC1VqD5CmgLu2p8RhN61gzJoWfWfpetD46mm5QacyOMLr90kMyPDB4vL2aE5gPU2SlG1nuIoHTr3VZI_-WStLeiV_RcJcm)

Before you begin, set up and test the example in [Pass runtime data in a JWT signed with PEM then encrypted with a symmetric key](data-downstream.html#runtime-sign-then-encrypt).

1. Add a second route to PingGateway, replacing value of the property `secretsDir` with the directory for the PEM files:

   * Linux

     `$HOME/.openig/config/routes/jwt-validate.json`

   * Windows

     `%appdata%\OpenIG\config\routes\jwt-validate.json`

   ```json
   {
     "name": "jwt-validate",
     "condition": "${find(request.uri.path, '^/jwt-validate')}",
     "properties": {
       "secretsDir": "path/to/secrets"
     },
     "capture": "all",
     "heap": [
       {
         "name": "SystemAndEnvSecretStore",
         "type": "SystemAndEnvSecretStore",
         "config": {
           "mappings": [{
             "secretId": "id.decrypted.key.for.signing.jwt",
             "format": "BASE64"
           }]
         }
       },
       {
         "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": [{
           "type": "JwtValidationFilter",
           "config": {
             "jwt": "${request.cookies['my-jwt'][0].value}",
             "secretsProvider": "FileSystemSecretStore-1",
             "decryptionSecretId": "symmetric.key.for.encrypting.jwt",
             "customizer": {
               "type": "ScriptableJwtValidatorCustomizer",
               "config": {
                 "type": "application/x-groovy",
                 "source": [
                   "builder.claim('name', JsonValue::asString, isEqualTo('demo'))",
                   "builder.claim('email', JsonValue::asString, isEqualTo('demo@example.com'));"
                 ]
               }
             },
             "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"
                 ]
               }
             }
           }
         }],
         "handler": {
           "type": "StaticResponseHandler",
           "config": {
             "status": 200,
             "headers": {
               "Content-Type": [ "text/html; charset=UTF-8" ]
             },
             "entity": [
               "<html>",
               "  <h2>Validated JWT:</h2>",
               "    <p>${contexts.jwtValidation.value}</p>",
               "  <h2>JWT payload:</h2>",
               "    <p>${contexts.jwtValidation.info}</p>",
               "</html>"
             ]
           }
         }
       }
     }
   }
   ```

   Source: [jwt-validate.json](../_attachments/config/routes/jwt-validate.json)

   Notice the following features of the route:

   * The route matches requests to `/jwt-validate`.

   * The JwtValidationFilter takes the value of the JWT from `my-jwt`.

   * The SystemAndEnvSecretStore, PemPropertyFormat, and FileSystemSecretStore objects in the heap are the same as those in the route to create the JWT. The JwtValidationFilter uses the same objects to validate the JWT.

   * The JwtBuilderFilter `customizer` requires that the JWT claims match `name:demo` and `email:demo@example.com`.

   * If the JWT is validated, the StaticResponseHandler displays the validated value. Otherwise, the FailureHandler displays the reason for the failed validation.

2. Test the setup:

   1. In your browser's privacy or incognito mode, go to <https://ig.example.com:8443/jwtbuilder-sign-then-encrypt> and accept the server certificate to build a JWT.

   2. Sign on to AM as user `demo`, password `Ch4ng31t`. The sample application displays the signed JWT.

   3. Go to <https://ig.example.com:8443/jwt-validate> to validate the JWT. The validated JWT and its payload are displayed.

   4. Test the setup again, but sign on to AM as a different user, or change the email address of the demo user in AM. The JWT isn't validated, and an error is displayed.
