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

POST 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

GET 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

POST 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

GET 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

GET 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 " 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.
" 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] } }
Published Oct 23, 2017
Version 1.0

Was this article helpful?

8 Comments

  • 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

     

  • 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 "

    session.server.initial_req_body
    " I can reuse in the first authenticated request on the SP.

  • Hi Stanislas,

     

    Thank you for your suggestion. I will update the code with that command.

     

    Yann

     

  • 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,

     

    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]).

     

    you can use the following action to remove your parameters :

     

    set action [string trimrigth [getfield HTTP::uri "ct=" 1] ?&]
    

    limit ACCESS::session usage :

     

    change the first condition with :

     

    if { ![set active_session [ACCESS::session exists [HTTP::cookie MRHSession]]]
    

    then for next conditions:

     

      if { $active_session && ...
    
  • Hi Stanislas,

     

    Thank you for your feedback. I will review the enhancement you are suggesting.

     

    I keep you in touch

     

    Yann

     

  • 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,

     

    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