Encrypt and Share JWT Sessions

JwtSession objects store session information in JWT cookies on the user-agent. The following sections describe how to set authenticated encryption for JwtSession, using symmetric keys.

Authenticated encryption encrypts data and then signs it with HMAC, in a single step. For more information, see Authenticated Encryption. For information about JwtSession, see JwtSession.

Encrypt JWT Sessions

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

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

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

    • Linux

    • Windows

    $HOME/.openig/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",
          "storePassword": "keystore.secret.id",
          "secretsProvider": ["SystemAndEnvSecretStore-1"],
          "mappings": [{
            "secretId": "jwtsession.symmetric.secret.id",
            "aliases": ["symmetric-key"]
          }]
        }
      },
        {
          "name": "SystemAndEnvSecretStore-1",
          "type": "SystemAndEnvSecretStore"
        }
      ],
      "session": {
        "type": "JwtSession",
        "config": {
          "authenticatedEncryptionSecretId": "jwtsession.symmetric.secret.id",
          "encryptionMethod": "A256CBC-HS512",
          "secretsProvider": ["KeyStoreSecretStore-1"],
          "cookie": {
            "name": "IG",
            "domain": ".example.com"
          }
        }
      },
      "handler": {
        "type": "StaticResponseHandler",
        "config": {
          "status": 200,
          "reason": "OK",
          "headers": {
            "Content-Type": [ "text/plain" ]
          },
          "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 JwtSession uses the KeyStoreSecretStore in the heap to manage the session encryption secret.

  3. In the terminal where you will run the IG 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 Session Between Multiple Instances of IG

When a session is shared between multiple instances of IG, the instances are able to share the session information for load balancing and failover.

This section gives an example of how to set up a deployment with three instances of IG that share a JwtSession.

Three instances of IG share a JwtSession.

In this example, IG is running in web container mode.

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

    $ keytool \
      -genseckey \
      -alias symmetric-key \
      -keystore /path/to/secrets/jwtsessionkeystore.pkcs12 \
      -storepass password \
      -storetype pkcs12 \
      -keyalg HmacSHA512 \
      -keysize 512
    Because KeyStore converts all characters in its key aliases to lower case, use only lowercase in alias definitions of a KeyStore.
  2. Set up and start the first instance of IG, which acts as the load balancer:

    • Download and install the instance to /path/to/instance1.

    • Create a configuration directory for the instance:

      $ mkdir $HOME/.instance1/
      1. Add the following route to IG:

        • Linux

        • Windows

        $HOME/.openig/routes/instance1-loadbalancer.json
        appdata\OpenIG\config\routes\instance1-loadbalancer.json
        {
          "name": "instance1-loadbalancer",
          "heap": [{
            "name": "KeyStoreSecretStore-1",
            "type": "KeyStoreSecretStore",
            "config": {
              "file": "/path/to/secrets/jwtsessionkeystore.pkcs12",
              "storeType": "PKCS12",
              "storePassword": "keystore.secret.id",
              "secretsProvider": ["SystemAndEnvSecretStore-1"],
              "mappings": [{
                "secretId": "jwtsession.symmetric.secret.id",
                "aliases": ["symmetric-key"]
              }]
            }
          },
            {
              "name": "SystemAndEnvSecretStore-1",
              "type": "SystemAndEnvSecretStore"
            }
          ],
          "session": {
            "type": "JwtSession",
            "config": {
              "authenticatedEncryptionSecretId": "jwtsession.symmetric.secret.id",
              "encryptionMethod": "A256CBC-HS512",
              "secretsProvider": ["KeyStoreSecretStore-1"],
              "cookie": {
                "name": "IG",
                "domain": ".example.com"
              }
            }
          },
          "handler": {
            "type": "DispatchHandler",
            "config": {
              "bindings": [{
                "condition": "${matches(request.uri.path, '/webapp/browsing') and (contains(request.uri.query, 'one') or empty(request.uri.query))}",
                "baseURI": "http://openig.example.com:8082",
                "handler": "ReverseProxyHandler"
              }, {
                "condition": "${matches(request.uri.path, '/webapp/browsing') and contains(request.uri.query, 'two')}",
                "baseURI": "http://openig.example.com:8083",
                "handler": "ReverseProxyHandler"
              }, {
                "condition": "${matches(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" ]
                        },
                        "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.

  1. In the terminal where you will run the IG 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.

    • Start the instance on port 8001:

      $ java -jar start.jar -Djetty.http.port=8001 -Dig.instance.dir=$HOME/.instance1/
  2. Set up and start the second instance of IG:

    • Download and install the instance to /path/to/instance2

    • Create a configuration directory for the instance:

      $ mkdir $HOME/.instance2/
    • Add the following route as $HOME/.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",
            "storePassword": "keystore.secret.id",
            "secretsProvider": ["SystemAndEnvSecretStore-1"],
            "mappings": [{
              "secretId": "jwtsession.symmetric.secret.id",
              "aliases": ["symmetric-key"]
            }]
          }
        },
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          }
        ],
        "session": {
          "type": "JwtSession",
          "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" ]
            },
            "entity": "<html><body>${session.authUsername!= null?'Hello, '.concat(session.authUsername).concat(' !'):'Session.authUsername is not defined'}! (instance2)</body></html>"
          }
        },
        "condition": "${matches(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. In the terminal where you will run the IG 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.

    • Start the instance on port 8082:

      $ java -jar start.jar -Djetty.http.port=8082 -Dig.instance.dir=$HOME/.instance2/
  4. Set up and start the third instance of IG:

    • Download and install the instance to /path/to/instance3

    • Create the configuration directory:

      $ mkdir $HOME/.instance3/
    • Add the following route as $HOME/.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",
            "storePassword": "keystore.secret.id",
            "secretsProvider": ["SystemAndEnvSecretStore-1"],
            "mappings": [{
              "secretId": "jwtsession.symmetric.secret.id",
              "aliases": ["symmetric-key"]
            }]
          }
        },
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          }
        ],
        "session": {
          "type": "JwtSession",
          "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" ]
             },
             "entity": "<html><body>${session.authUsername!= null?'Hello, '.concat(session.authUsername).concat(' !'):'Session.authUsername is not defined'}! (instance3)</body></html>"
          }
        },
        "condition": "${matches(request.uri.path, '/webapp/browsing')}",
        "capture": "all"
      }

      Notice that the route is the same as instance2.json, apart from the text in the entity of the StaticResponseHandler.

  5. In the terminal where you will run the IG 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.

    • Start the instance on port 8083:

      $ java -jar start.jar -Djetty.http.port=8083 -Dig.instance.dir=$HOME/.instance3/
  6. Test the setup:

    • Access instance 1, to generate a session:

      $ curl -v http://openig.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)
    • Using the JWT cookie returned in the previous step, access the instance 2:

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

      Note that instance 2 can access the session info.

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

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

      Note that instance 3 can access the session info.