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 PingGateway instances.
The following image shows a simplified data flow for POST data preservation:
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, PingGateway 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
Before you start, set up and test the example in Cross-domain single sign-on. This example extends that example.
-
In PingGateway, 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.
-
-
Add the following route to PingGateway:
-
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>
-
-
Test the setup:
-
In your browser’s privacy or incognito mode, go to https://ig.ext.com:8443/form.
The script in the StaticResponseHandler entity of
form.json
creates a button to demonstrate form posting. -
Press the button, and log in to AM as user
demo
, passwordCh4ng31t
.When you have authenticated, the script presents the POST data from the original request.
-