Identity Gateway 2023.11

POST data preservation

The DataPreservationFilter triggers POST data preservation when an unauthenticated client posts HTML form data to a protected resource.

When an authentication redirect is triggered, the filter stores the data in the HTTP session, and redirects the client for authentication. After authentication, the filter generates an empty self-submitting form POST to emulate the original POST. It then replays the stored data into the request before passing it along the chain.

The data can be any POST content, such as HTML form data or a file upload.

Consider the following points for POST data preservation:

  • The size of the POST data is important because the data is stored in the HTTP session.

  • Stateless sessions store form content in encrypted JWT session cookies. To prevent requests from being rejected because the HTTP headers are too long, configure connectors:maxTotalHeadersSize in admin.json.

  • Sticky sessions may be required for deployments with stateful sessions, and multiple IG instances.

The following image shows a simplified data flow for POST data preservation:

pdp-flow

1. An unauthenticated client requests a POST to a protected resource.

2. The DataPreservationFilter tags the the request with a unique identifier, and passes it along the chain. The next filter should be an authentication filter such as a SingleSignOnFilter.

3. The next filter triggers the authentication, and includes a goto URL tagged with the unique identifier from the previous step.

4-5. The DataPreservationFilter stores the POST data in the HTTP session, and redirects the request for authentication. The POST data is identified by the unique identifier.

6-7. The client authenticates with AM, and AM provides an authentication response to the goto URL.

8. The authenticated client sends a GET request containing the unique identifier.

9-10. The DataPreservationFilter validates the unique identifier, and generates a self-posting form response for the client.

The presence of the unique identifier in the goto URL ensures that requests at the URL can be individually identified. Additionally, it is more difficult to hijack user data, because there is little chance of guessing the code within the login window.

If the identifier is not validated, IG denies the request.

11. The client resends the POST request, including the identifier.

12-13. The DataPreservationFilter updates the request with the POST data, and sends it along the chain.

Preserve POST data during CDSSO

  1. Set up AM and IG as described in Authentication, and test the example. This example extends that example.

  2. Replace cdsso.json with the following route:

    • Linux

    • Windows

    $HOME/.openig/config/routes/pdp.json
    %appdata%\OpenIG\config\routes\pdp.json
    {
      "name": "pdp",
      "baseURI": "http://app.example.com:8081",
      "condition": "${find(request.uri.path, '^/home/cdsso')}",
      "heap": [
        {
          "name": "SystemAndEnvSecretStore-1",
          "type": "SystemAndEnvSecretStore"
        },
        {
          "name": "AmService-1",
          "type": "AmService",
          "config": {
            "url": "http://am.example.com:8088/openam",
            "realm": "/",
            "agent": {
              "username": "ig_agent_cdsso",
              "passwordSecretId": "agent.secret.id"
            },
            "secretsProvider": "SystemAndEnvSecretStore-1",
            "sessionCache": {
              "enabled": false
            }
          }
        }
      ],
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [
            {
              "name": "DataPreservationFilter",
              "type": "DataPreservationFilter"
            },
            {
              "name": "CrossDomainSingleSignOnFilter-1",
              "type": "CrossDomainSingleSignOnFilter",
              "config": {
                "redirectEndpoint": "/home/cdsso/redirect",
                "authCookie": {
                  "path": "/home",
                  "name": "ig-token-cookie"
                },
                "amService": "AmService-1",
                "logoutExpression": "${find(request.uri.query, 'logOff=true')}",
                "defaultLogoutLandingPage": "/form"
              }
            }
          ],
          "handler": {
            "type": "StaticResponseHandler",
            "config": {
              "status": 200,
              "headers": {
                "Content-Type": [
                  "text/html; charset=UTF-8"
                ]
              },
              "entity": [
                "<html>",
                "  <body>",
                "    <h1>Request Information</h1>",
                "      <p>Request method: #{request.method}",
                "      <p>Request URI: #{request.uri}",
                "      <p>Query string: #{request.queryParams}",
                "      <p>Form: #{request.entity.form}",
                "      <p>Content length: #{request.headers['Content-Length'][0]}",
                "      <p>Content type: #{request.headers['Content-Type'][0]}",
                "  </body>",
                "</html>"
              ]
            }
          }
        }
      }
    }

    Notice the following differences compared to cdsso.json:

    • A DataPreservationFilter is positioned in front of the CrossDomainSingleSignOnFilter to manage POST data preservation before authentication.

    • The ReverseProxyHandler is replaced by a StaticResponseHandler, which displays the POST data provided in the request.

When verificationSecretId is not configured, IG discovers and uses the AM JWK set to verify the signature of AM session tokens. If the JWK set isn’t available, IG does not verify the tokens.
  1. Add the following route to IG:

    • Linux

    • Windows

    $HOME/.openig/config/routes/form.json
    %appdata%\OpenIG\config\routes\pdp.json
    {
      "condition": "${request.uri.path == '/form'}",
      "handler": {
        "type": "StaticResponseHandler",
        "config": {
          "status": 200,
          "headers": {
            "Content-Type": [ "text/html" ]
          },
          "entity" : [
            "<html>",
            "  <body>",
            "    <h1>Test page : POST Data Preservation containing visible and hidden form elements</h1>",
            "    <form id='testingPDP' enctype='application/x-www-form-urlencoded' name='test_form' action='/home/cdsso/pdp.info?foo=bar&baz=pdp' method='post'>",
            "      <input name='email' value='user@example.com' size='60'>",
            "      <input type='hidden' name='phone' value='555-123-456'/>",
            "      <input type='hidden' name='manager' value='Bob'/>",
            "      <input type='hidden' name='dept' value='Engineering'/>",
            "      <input type='submit' value='Press to demo form posting' id='form_post_button'/>",
            "    </form>",
            "  </body>",
            "</html>"
          ]
        }
      }
    }

    Notice the following features of the route:

    • The route matches requests to /home/form.

    • The StaticResponseHandler includes the following entity to present visible and hidden form elements from the original request:

      <!DOCTYPE html>
      <html>
         <body>
            <h1>Test page : POST Data Preservation containing visible and hidden form elements</h1>
            <form
               id='testingPDP'
               enctype='application/x-www-form-urlencoded'
               name='test_form'
               action='/home/cdsso/pdp.info?foo=bar&baz=pdp'
               method='post'>
               <input
                  name='email'
                  value='user@example.com'
                  size='60'>
               <input
                  type='hidden'
                  name='phone'
                  value='555-123-456'/>
               <input
                  type='hidden'
                  name='manager'
                  value='Bob'/>
               <input
                  type='hidden'
                  name='dept'
                  value='Engineering'/>
               <input
                  type='submit'
                  value='Press to demo form posting'
                  id='form_post_button'/>
            </form>
         </body>
      </html>
  2. Test the setup:

    1. If you are logged in to AM, log out and clear any cookies.

    2. Go to https://ig.ext.com:8443/form.

      If you see warnings that the site is not secure, respond to the warnings to access the site.

      The script in the StaticResponseHandler entity of form.json creates a button to demonstrate form posting.

    3. Press the button, and log in to AM as user demo, password Ch4ng31t.

      When you have authenticated, the script presents the POST data from the original request.