Support for POST preservation when APM Multidomain SSO is configured
Problem this snippet solves:
F5 doesn't support the preservation of the initial POST request when the Virtual Server has an access profile configured for Multidomain SSO. After authentication, the user is redirected to the initial URL endpoint and issue a GET request instead of a POST request.
How to use this snippet:
We share a code sample to demonstrate how to support POST preservation in this typical scenario. This irule may need some additional configuration and settings to work properly.
As a PoC, we configured two endpoints : sp.expertlab.net and idp.expertlab.net on the same Virtual Server and access profile. The access profile is configured for Multidomain SSO and we prompt the user for a form based authentication.
We also added a dummy form prompted to the user after authentication to simplify our testing.
Please note that the irule has been successfully tested with Chrome and Firefox. We are still running tests for Internet Explorer and Edge Browsers.
Note : APM behave differently between v11, v12 and v13. To make POST preservation work for v11 and v12, you need to add the following variable assign settings before the Allow ending in your access policy :
- session.server.body = Session Variable session.server.initial_req_body
- session.policy.result.redirect.url = Session Variable session.server.landinguri_base64
Please note that the order of variable assignment is very important. Moreover, you need to change the name of the request body session variable in the irule too (static::body_var)
Flow in v13.x
POST https://sp.example.net/post_action.php
307 Temporary Redirect - Location: https://idp.example.net/F5Networks-SSO-Req?SSO_ORIG_URI=aHR0cHM6Ly9zcC5leGFtcGxlLm5ldC9wb3N0X2FjdGlvbi5waHA
302 Found - Location: /my.policy
GET https://idp.example.net/my.policy
200 OK
POST https://idp.example.net/my.policy
302 Found - Location: https://sp.example.net/F5Networks-SSO-Resp?SSO_ORIG_URI=aHR0cHM6Ly9zcC5leGFtcGxlLm5ldC9wb3N0X2FjdGlvbi5waHA&TOKEN=123954
302 Found - Location: https://sp.example.net/post_action.php?ct=application%2Fx-www-form-urlencoded
GET https://sp.example.net/post_action.php?ct=application%2Fx-www-form-urlencoded
200 OK - Body contains JS to force an auto-POST action
POST https://sp.example.net/post_action.php
...
Flow in v12.x
POST https://sp.example.net/post_action.php
307 Temporary Redirect - Location: https://idp.example.net/F5Networks-SSO-Req?SSO_ORIG_URI=aHR0cHM6Ly9zcC5leGFtcGxlLm5ldC9wb3N0X2FjdGlvbi5waHA
302 Found - Location: /my.policy
GET https://idp.example.net/my.policy
200 OK
POST https://idp.example.net/my.policy
302 Found - Location: https://idp.example.net/F5Networks-SSO-Req?SSO_ORIG_URI=aHR0cHM6Ly9zcC5leGFtcGxlLm5ldC9wb3N0X2FjdGlvbi5waHA
302 Found - Location: https://sp.example.net/F5Networks-SSO-Resp?SSO_ORIG_URI=aHR0cHM6Ly9zcC5leGFtcGxlLm5ldC9wb3N0X2FjdGlvbi5waHA&TOKEN=123954
302 Found - Location: https://sp.example.net/post_action.php?ct=application%2Fx-www-form-urlencoded
GET https://sp.example.net/post_action.php?ct=application%2Fx-www-form-urlencoded
200 OK - Body contains JS to force an auto-POST action
POST https://sp.example.net/post_action.php
...
Code :
### # POST preservation feature # for Virtual Server with Multidomain SSO configured # # require : APM ### ### # Special notes # To support POST preservation in v11 and v12, # the administrator needs to configure special session variable assignment before the Allow ending in a Access policy # session.server.body = Session Variable session.server.initial_req_body # session.policy.result.redirect.url = Session Variable session.server.landinguri_base64 ### ### # Release notes # # 2017/11/23 # * Basic support for POST preservation in v13 # * Add support for v11 and v12 environments # # 2017/11/24 # * Replace static::idp_host by [PROFILE::access primary_auth_service] # * Add a static var to enable or disable the dummy form designed for testing purposes # * Avoid POSTing real body multiple times. A dummy var is used to retrieve the original POST content # # 2017/11/25 # * Remove some coding errors # * Refactoring of some parts of the irule ### when RULE_INIT { set static::md_start_uri "/F5Networks-SSO-Req?SSO_ORIG_URI=" # for v11.x and v12.x deployment # set static::body_var "session.server.body" # for v13.x deployment set static::body_var "session.server.initial_req_body" # enable or disable autogenerated testing forms set static::dummy_form 1 } when HTTP_REQUEST { if { ![ACCESS::session exists [HTTP::cookie MRHSession]] and !([HTTP::path] eq "/F5Networks-SSO-Resp") } { if { [HTTP::method] eq "POST" } { # save post data set ct [HTTP::header Content-Type] set uri [HTTP::uri] if { [URI::query $uri] != "" } { set uri $uri&ct=[URI::encode $ct]&f5-mdsso-post=1 } else { set uri $uri?ct=[URI::encode $ct]&f5-mdsso-post=1 } HTTP::respond 307 noserver Location "[PROFILE::access primary_auth_service]$static::md_start_uri[URI::encode [b64encode https://[HTTP::host]$uri]]" Connection Close return } else { HTTP::respond 302 noserver Location "[PROFILE::access primary_auth_service]$static::md_start_uri[URI::encode [b64encode https://[HTTP::host][HTTP::uri]]]" Connection Close return } } if { [ACCESS::session exists [HTTP::cookie MRHSession]] and [HTTP::query] contains "f5-mdsso-post=1" and [ACCESS::session data get $static::body_var] != "" } { set ct [URI::decode [URI::query [HTTP::uri] ct]] set dummy [getfield [expr {rand()}] "." 2] ACCESS::session data set session.server.dummy $dummy ACCESS::session data set session.server.ct $ct HTTP::respond 200 content "" noserver Content-Type "text/html" return } if { [ACCESS::session exists [HTTP::cookie MRHSession]] and [HTTP::method] eq "POST" and [HTTP::payload] contains "dummy" and [ACCESS::session data get session.server.dummy] eq [URI::query "/?[HTTP::payload]" dummy] } { HTTP::header replace Content-Type [ACCESS::session data get session.server.ct] HTTP::payload replace 0 [HTTP::header Content-Length] [ACCESS::session data get $static::body_var] } }this page is used to hold your data while you are being authorized for your request.
you will be forwarded to continue the authorization process. if this does not happen automatically, please click the continue button below.
Hi Stanislas,
Sure it works for the initial POST request. I remember that the behavior was different in v12 and v13. From my memory, I would say that in v13, POST preservation is native for sub requests in an existing session but not sure 100%.
I should do further tests on my lab environment.
Otherwise, you have to track the request and reply with multiples 307 responses until you get a valid token and session cookie.
Keep you in touch
Regards
Yann
- Stanislas_Piro2Cumulonimbus
Hi Yann,
I tried to apply your code for a customer but I faced some limitation.
This code only works for the first request starting session.
I had the following behavior :
- user request sharepoint website (not authenticated yet) with GET --> redirect to login page
- user request login page with GET and authenticate --> redirect to sharepoint website
- user want to edit with Office Web App behind APM, first request is a POST --> redirect to login page
- user request login page with GET (already authenticated) --> redirect to Office Web App
- browser send a GET request --> file edition fails
- user refresh content --> file edition fails
I tried this code but session.server.body is only set for first request starting the session.
did you ever get this issue? did you try to solve it?
Hi Stanislas,
Thank you for your feedback. I will review the enhancement you are suggesting.
I keep you in touch
Yann
- Stanislas_Piro2Cumulonimbus
Hi,
Really nice work. I never saw How POST requests were handled for new session. you made this code have almost the same behavior as single domain SSO!
here are some code improvements:
add query string parameters (with or without query string parameters):
set uri [HTTP::path]?[join "[HTTP::query] ct=[URI::encode [HTTP::header Content-Type]] f5-mdsso-post=1" &]
In your http code to resubmit, you don't manage existing query string (the action is [HTTP::path]).
set action [string trimrigth [getfield HTTP::uri "ct=" 1] ?&]
limit ACCESS::session usage :
if { ![set active_session [ACCESS::session exists [HTTP::cookie MRHSession]]]
then for next conditions:
if { $active_session && ...
Hi Stanislas,
it works fine. Here an example. This use case is correctly managed by F5 on the IDP side.
POST /action_page.php HTTP/1.1 Host: sp.expertlab.net Connection: keep-alive Content-Length: 31 Pragma: no-cache Cache-Control: no-cache Origin: http://sp.expertlab.net Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/60.0.3112.113 Chrome/60.0.3112.113 Safari/537.36 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8 Referer: http://sp.expertlab.net/test Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.8 Cookie: LastMRH_Session=13f5b45f; MRHSession=c2ba3c8cb4fa94f73a5a677513f5b45f
HTTP/1.0 307 Temporary Redirect Location: http://idp.expertlab.net/F5Networks-SSO-Req?SSO_ORIG_URI=aHR0cDovL3NwLmV4cGVydGxhYi5uZXQvYWN0aW9uX3BhZ2UucGhwP2N0PWFwcGxpY2F0aW9uJTJmeC13d3ctZm9ybS11cmxlbmNvZGVk Connection: close Content-Length: 0
POST /F5Networks-SSO-Req?SSO_ORIG_URI=aHR0cDovL3NwLmV4cGVydGxhYi5uZXQvYWN0aW9uX3BhZ2UucGhwP2N0PWFwcGxpY2F0aW9uJTJmeC13d3ctZm9ybS11cmxlbmNvZGVk HTTP/1.1 Host: idp.expertlab.net Connection: keep-alive Content-Length: 31 Pragma: no-cache Cache-Control: no-cache Origin: null Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/60.0.3112.113 Chrome/60.0.3112.113 Safari/537.36 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8 Referer: http://sp.expertlab.net/test Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.8 Cookie: F5_ST=1z1z1z1508964180z604800; LastMRH_Session=b9303aab; MRHSession=8fff3a92fa20f06f74771e92b9303aab
HTTP/1.1 307 Temporary Redirect Server: BigIP Connection: Close Content-Length: 0 Location: http://sp.expertlab.net/F5Networks-SSO-Resp?SSO_ORIG_URI=aHR0cDovL3NwLmV4cGVydGxhYi5uZXQvYWN0aW9uX3BhZ2UucGhwP2N0PWFwcGxpY2F0aW9uJTJmeC13d3ctZm9ybS11cmxlbmNvZGVk&TOKEN=261ef078 Set-Cookie: LastMRH_Session=b9303aab;path=/ Set-Cookie: MRHSession=8fff3a92fa20f06f74771e92b9303aab;path=/
POST /F5Networks-SSO-Resp?SSO_ORIG_URI=aHR0cDovL3NwLmV4cGVydGxhYi5uZXQvYWN0aW9uX3BhZ2UucGhwP2N0PWFwcGxpY2F0aW9uJTJmeC13d3ctZm9ybS11cmxlbmNvZGVk&TOKEN=261ef078 HTTP/1.1 Host: sp.expertlab.net Connection: keep-alive Content-Length: 31 Pragma: no-cache Cache-Control: no-cache Origin: null Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/60.0.3112.113 Chrome/60.0.3112.113 Safari/537.36 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8 Referer: http://sp.expertlab.net/test Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.8 Cookie: LastMRH_Session=13f5b45f; MRHSession=c2ba3c8cb4fa94f73a5a677513f5b45f
HTTP/1.1 307 Temporary Redirect Server: BigIP Connection: Close Content-Length: 0 Location: http://sp.expertlab.net/action_page.php?ct=application%2fx-www-form-urlencoded Set-Cookie: LastMRH_Session=b9303aab;path=/ Set-Cookie: MRHSession=8fff3a92fa20f06f74771e92b9303aab;path=/
POST /action_page.php?ct=application%2fx-www-form-urlencoded HTTP/1.1 Host: sp.expertlab.net Connection: keep-alive Content-Length: 31 Pragma: no-cache Cache-Control: no-cache Origin: null Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/60.0.3112.113 Chrome/60.0.3112.113 Safari/537.36 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8 Referer: http://sp.expertlab.net/test Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.8 Cookie: LastMRH_Session=b9303aab; MRHSession=8fff3a92fa20f06f74771e92b9303aab
firstname=Mickey&lastname=Mouse
HTTP/1.0 200 OK Content-Type: text/html Server: BigIP Connection: Keep-Alive Content-Length: 324
Hi Stanislas,
Thank you for your suggestion. I will update the code with that command.
Yann
Hi Stanislas,
I'm responding with a 307 to the initial request. Thus, the client makes a POST request to the IDP. The IDP then stores a new session variable called "
" I can reuse in the first authenticated request on the SP.session.server.initial_req_body
Hi Stanislas,
Hope you are doing well. Nice irule!
I write this irule to manage POST request when the user session has already expired.
Can you confirm that your irule code is able to manage this use case ?
Bye
Yann