PingGateway 2024.11

Encrypt and share JWT sessions

PingGateway stateless sessions store session information in JWT cookies on the user-agent. Learn more in Sessions.

This page describes how to set authenticated encryption for stateless sessions using symmetric keys.

With authenticated encryption, PingGateway encrypts data and signs it with HMAC in a single step.

Encrypt JWT sessions

This section describes how to set up a keystore with a symmetric key for authenticated encryption of a JWT session.

  1. Set up a keystore to contain the encryption key, where the keystore and the key have the password password:

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

      $ cd /path/to/secrets
    2. Generate the key:

      $ keytool \
        -genseckey \
        -alias symmetric-key \
        -keystore jwtsessionkeystore.pkcs12 \
        -storepass password \
        -storetype pkcs12 \
        -keyalg HmacSHA512 \
        -keysize 512
      Because keytool converts all characters in its key aliases to lowercase, use only lowercase in alias definitions of a keystore.
  2. Add the following route to PingGateway:

    • Linux

    • Windows

    $HOME/.openig/config/routes/jwt-session-encrypt.json
    %appdata%\OpenIG\config\routes\jwt-session-encrypt.json
    {
      "name": "jwt-session-encrypt",
      "heap":  [{
        "name": "KeyStoreSecretStore-1",
        "type": "KeyStoreSecretStore",
        "config": {
          "file": "/path/to/secrets/jwtsessionkeystore.pkcs12",
          "storeType": "PKCS12",
          "storePasswordSecretId": "keystore.secret.id",
          "secretsProvider": ["SystemAndEnvSecretStore-1"],
          "mappings": [{
            "secretId": "jwtsession.symmetric.secret.id",
            "aliases": ["symmetric-key"]
          }]
        }
      },
        {
          "name": "SystemAndEnvSecretStore-1",
          "type": "SystemAndEnvSecretStore"
        }
      ],
      "session": {
        "type": "JwtSessionManager",
        "config": {
          "authenticatedEncryptionSecretId": "jwtsession.symmetric.secret.id",
          "encryptionMethod": "A256CBC-HS512",
          "secretsProvider": ["KeyStoreSecretStore-1"],
          "cookie": {
            "name": "IG",
            "domain": ".example.com"
          }
        }
      },
      "handler": {
        "type": "StaticResponseHandler",
        "config": {
          "status": 200,
          "headers": {
            "Content-Type": [ "text/plain; charset=UTF-8" ]
          },
          "entity": "Hello world!"
        }
      },
      "condition": "${request.uri.path == '/jwt-session-encrypt'}"
    }

    Notice the following features of the route:

    • The route matches requests to /jwt-session-encrypt.

    • The KeyStoreSecretStore uses the SystemAndEnvSecretStore in the heap to manage the store password.

    • The JWTSessionManager uses the KeyStoreSecretStore in the heap to manage the session encryption secret.

  3. In the terminal where you will run the PingGateway instance, create an environment variable for the value of the keystore password:

    $ export KEYSTORE_SECRET_ID='cGFzc3dvcmQ='

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

Share JWT sessions between multiple instances of PingGateway

This section shows how to set up a deployment with three PingGateway servers sharing stateless sessions.

Three PingGateway servers sharing a stateless session
  1. Set up a keystore to contain the encryption key, where the keystore and the key have the password password:

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

      $ cd /path/to/secrets
    2. Generate the key:

      $ keytool \
        -genseckey \
        -alias symmetric-key \
        -keystore jwtsessionkeystore.pkcs12 \
        -storepass password \
        -storetype pkcs12 \
        -keyalg HmacSHA512 \
        -keysize 512
      Because keytool converts all characters in its key aliases to lowercase, use only lowercase in alias definitions of a keystore.
  2. Prepare the PingGateway installation:

    1. Create an installation directory for PingGateway in /path/to.

    2. Download and unzip PingGateway-2024.11.0.zip in /path/to, as described in the Install. The directory /path/to/identity-gateway-2024.11.0 is created.

  3. Set up the first instance of PingGateway, which acts as the load balancer:

    1. Create a configuration directory for the instance and go to it:

      $ mkdir -p /path/to/config-instance1/config/routes
    2. Add the following route:

      • Linux

      • Windows

      /path/to/config-instance1/config/routes/instance1-loadbalancer.json
      %appdata%\path\to\config-instance1\config\routes\instance1-loadbalancer.json
      {
        "name": "instance1-loadbalancer",
        "heap": [{
          "name": "KeyStoreSecretStore-1",
          "type": "KeyStoreSecretStore",
          "config": {
            "file": "/path/to/secrets/jwtsessionkeystore.pkcs12",
            "storeType": "PKCS12",
            "storePasswordSecretId": "keystore.secret.id",
            "secretsProvider": ["SystemAndEnvSecretStore-1"],
            "mappings": [{
              "secretId": "jwtsession.symmetric.secret.id",
              "aliases": ["symmetric-key"]
            }]
          }
        },
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          }
        ],
        "session": {
          "type": "JwtSessionManager",
          "config": {
            "authenticatedEncryptionSecretId": "jwtsession.symmetric.secret.id",
            "encryptionMethod": "A256CBC-HS512",
            "secretsProvider": ["KeyStoreSecretStore-1"],
            "cookie": {
              "name": "IG",
              "domain": ".example.com"
            }
          }
        },
        "handler": {
          "type": "DispatchHandler",
          "config": {
            "bindings": [{
              "condition": "${find(request.uri.path, '/webapp/browsing') and (contains(request.uri.query, 'one') or empty(request.uri.query))}",
              "baseURI": "http://ig.example.com:8002",
              "handler": "ReverseProxyHandler"
            }, {
              "condition": "${find(request.uri.path, '/webapp/browsing') and contains(request.uri.query, 'two')}",
              "baseURI": "http://ig.example.com:8003",
              "handler": "ReverseProxyHandler"
            }, {
              "condition": "${find(request.uri.path, '/log-in-and-generate-session')}",
              "handler": {
                "type": "Chain",
                "config": {
                  "filters": [{
                    "type": "AssignmentFilter",
                    "config": {
                      "onRequest": [{
                        "target": "${session.authUsername}",
                        "value": "Sam Carter"
                      }]
                    }
                  }],
                  "handler": {
                    "type": "StaticResponseHandler",
                    "config": {
                      "status": 200,
                      "headers": {
                        "Content-Type": [ "text/html; charset=UTF-8" ]
                      },
                      "entity": "<html><body>Sam Carter logged IN. (JWT session generated)</body></html>"
                    }
                  }
                }
              }
            }]
          }
        },
        "capture": "all"
      }

      Notice the following features of the route:

      • The route has no condition, so it matches all requests.

      • When the request matches /log-in-and-generate-session, the DispatchHandler creates a JWT session, whose authUsername attribute contains the name Sam Carter.

      • When the request matches /webapp/browsing, the DispatchHandler dispatches the request to instance 2 or instance 3, depending on the rest of the request path.

    3. Add the following configuration:

      • Linux

      • Windows

      /path/to/config-instance1/config/admin.json
      %appdata%\path\to\config-instance1\config\admin.json
      {
        "connectors": [{
        "port": 8001
        }]
      }
    4. In the terminal where you will run the PingGateway instance, create an environment variable for the value of the keystore password:

      $ export KEYSTORE_SECRET_ID='cGFzc3dvcmQ='

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

    5. Start PingGateway:

      • Linux

      • Windows

      $ /path/to/identity-gateway-2024.11.0/bin/start.sh /path/to/config-instance1/
      C:\path\to\identity-gateway-2024.11.0\bin\start.bat %appdata%/path/to/config-instance1
  4. Set up and start the second instance of PingGateway:

    1. Create a configuration directory for the instance:

      $ mkdir -p /path/to/config-instance2/config/routes
    2. Add the following route:

      • Linux

      • Windows

      /path/to/config-instance2/config/routes/instance2-retrieve-session-username.json
      %appdata%\path\to\config-instance2\config\routes\instance2-retrieve-session-username.json
      {
        "name": "instance2-retrieve-session-username",
        "heap":  [{
          "name": "KeyStoreSecretStore-1",
          "type": "KeyStoreSecretStore",
          "config": {
            "file": "/path/to/secrets/jwtsessionkeystore.pkcs12",
            "storeType": "PKCS12",
            "storePasswordSecretId": "keystore.secret.id",
            "secretsProvider": ["SystemAndEnvSecretStore-1"],
            "mappings": [{
              "secretId": "jwtsession.symmetric.secret.id",
              "aliases": ["symmetric-key"]
            }]
          }
        },
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          }
        ],
        "session": {
          "type": "JwtSessionManager",
          "config": {
            "authenticatedEncryptionSecretId": "jwtsession.symmetric.secret.id",
            "encryptionMethod": "A256CBC-HS512",
            "secretsProvider": ["KeyStoreSecretStore-1"],
            "cookie": {
              "name": "IG",
              "domain": ".example.com"
            }
          }
        },
        "handler": {
          "type": "StaticResponseHandler",
          "config": {
            "status": 200,
            "headers": {
              "Content-Type": [ "text/html; charset=UTF-8" ]
            },
            "entity": [
              "<html>",
              "  <body>",
              "    ${session.authUsername!= null?'Hello, '.concat(session.authUsername).concat(' !'):'Session.authUsername is not defined'}! (instance2)",
              "  </body>",
              "</html>"
            ]
          }
        },
        "condition": "${find(request.uri.path, '/webapp/browsing')}",
        "capture": "all"
      }

      Notice the following features of the route compared to the route for instance 1:

      • The route matches the condition /webapp/browsing. When a request matches /webapp/browsing, the DispatchHandler dispatches it to instance 2.

      • The StaticResponseHandler displays information from the session context.

    3. Add the following configuration:

      • Linux

      • Windows

      /path/to/config-instance2/config/admin.json
      %appdata%\path\to\config-instance2\config\admin.json
      {
        "connectors": [{
        "port": 8002
        }]
      }
    4. In the terminal where you will run the PingGateway instance, create an environment variable for the value of the keystore password:

      $ export KEYSTORE_SECRET_ID='cGFzc3dvcmQ='

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

    5. Start PingGateway:

      • Linux

      • Windows

      $ /path/to/identity-gateway-2024.11.0/bin/start.sh /path/to/config-instance2/
      C:\path\to\identity-gateway-2024.11.0\bin\start.bat %appdata%/path/to/config-instance2
  5. Set up and start the third instance of PingGateway:

    1. Create a configuration directory:

      $ mkdir -p /path/to/config-instance3/config/routes
    2. Add the following route:

      • Linux

      • Windows

      /path/to/config-instance3/config/routes/instance3-retrieve-session-username.json
      %appdata%\path\to\config-instance3\config\routes\instance3-retrieve-session-username.json
      {
        "name": "instance3-retrieve-session-username",
        "heap":  [{
          "name": "KeyStoreSecretStore-1",
          "type": "KeyStoreSecretStore",
          "config": {
            "file": "/path/to/secrets/jwtsessionkeystore.pkcs12",
            "storeType": "PKCS12",
            "storePasswordSecretId": "keystore.secret.id",
            "secretsProvider": ["SystemAndEnvSecretStore-1"],
            "mappings": [{
              "secretId": "jwtsession.symmetric.secret.id",
              "aliases": ["symmetric-key"]
            }]
          }
        },
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          }
        ],
        "session": {
          "type": "JwtSessionManager",
          "config": {
            "authenticatedEncryptionSecretId": "jwtsession.symmetric.secret.id",
            "encryptionMethod": "A256CBC-HS512",
            "secretsProvider": ["KeyStoreSecretStore-1"],
            "cookie": {
              "name": "IG",
              "domain": ".example.com"
            }
          }
        },
        "handler": {
          "type": "StaticResponseHandler",
          "config": {
            "status": 200,
            "headers": {
              "Content-Type": [ "text/html; charset=UTF-8" ]
              },
              "entity": [
                "<html>",
                "  <body>",
                "    ${session.authUsername!= null?'Hello, '.concat(session.authUsername).concat(' !'):'Session.authUsername is not defined'}! (instance3)",
                "  </body>",
                "</html>"
              ]
            }
          },
        "condition": "${find(request.uri.path, '/webapp/browsing')}",
        "capture": "all"
      }

      Notice that the route is the same as for instance 2, apart from the text in the entity of the StaticResponseHandler.

    3. Add the following configuration:

      • Linux

      • Windows

      /path/to/config-instance3/config/admin.json
      %appdata%\path\to\config-instance3\config\admin.json
      {
        "connectors": [{
        "port": 8003
        }]
      }
    4. In the terminal where you will run the PingGateway instance, create an environment variable for the value of the keystore password:

      $ export KEYSTORE_SECRET_ID='cGFzc3dvcmQ='

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

    5. Start PingGateway:

      • Linux

      • Windows

      $ /path/to/identity-gateway-2024.11.0/bin/start.sh /path/to/config-instance3/
      C:\path\to\identity-gateway-2024.11.0\bin\start.bat %appdata%/path/to/config-instance3
  6. Test the setup:

    1. Access instance 1, to generate a session:

      $ curl -v http://ig.example.com:8001/log-in-and-generate-session
      
      GET /log-in-and-generate-session HTTP/1.1
      ...
      
      HTTP/1.1 200 OK
      Content-Length: 84
      Set-Cookie: IG=eyJ...HyI; Path=/; Domain=.example.com; HttpOnly
      ...
      Sam Carter logged IN. (JWT session generated)
    2. Using the JWT cookie returned in the previous step, access instance 2:

      $ curl -v http://ig.example.com:8001/webapp/browsing\?one --header "cookie:IG=eyJ...HyI"
      
      GET /webapp/browsing?one HTTP/1.1
      ...
      cookie: IG=eyJ...HyI
      ...
      HTTP/1.1 200 OK
      ...
      Hello, Sam Carter !! (instance2)

      Note that instance 2 can access the session info.

    3. Using the JWT cookie again, access instance 3:

      $ curl -v http://ig.example.com:8001/webapp/browsing\?two --header "cookie:IG=eyJ...HyI"
      
      GET /webapp/browsing?two HTTP/1.1
      ...
      cookie: IG=eyJ...HyI
      ...
      HTTP/1.1 200 OK
      ...
      Hello, Sam Carter !! (instance3)

      Note that instance 3 can access the session info.