APM Sharepoint authentication
Problem this snippet solves:
Updated version to support Webdav with windows explorer after Nicolas's comment.
APM is a great authentication service but it does it only with forms.
The default behavior is to redirect user to /my.policy to process VPE. this redirect is only supported for GET method.
Sharepoint provide 3 different access types:
- browsing web site with a browser
- Editing documents with Office
- browser folder with webdav client (or editing documents with libreoffice through webdav protocol)
This irule display best authentication method for each of these access types:
- browsers authenticate with default authentication method (form based authentication)
- Microsoft office authenticate with Form based authentication (with support of MS-OFBA protocol)
- Libreoffice and webdav clients authenticate with 401 basic authentication
Form based authentication (browser and Microsoft office) is compatible (validated for one customer) with SAML authentication
Editing documents is managed with a persistent cookie expiring after 5 minutes. to be shared between IE and Office, it requires :
- cookie is persistent (expiration date instead of deleted at the end of session)
- web site defined as "trusted sites" in IE.
How to use this snippet:
install this irule and enable it on the VS.
Code :
when RULE_INIT { array set static::MSOFBA { ReqHeader "X-FORMS_BASED_AUTH_REQUIRED" ReqVal "/sp-ofba-form" ReturnHeader "X-FORMS_BASED_AUTH_RETURN_URL" ReturnVal "/sp-ofba-completed" SizeHeader "X-FORMS_BASED_AUTH_DIALOG_SIZE" SizeVal "800x600" } set static::ckname "MRHSession_SP" set static::Basic_Realm_Text "SharePoint Authentication" } when HTTP_REQUEST { set apmsessionid [HTTP::cookie value MRHSession] set persist_cookie [HTTP::cookie value $static::ckname] set clientless_mode 0 set form_mode 0 # Identify User-Agents type if {[HTTP::header exists "X-FORMS_BASED_AUTH_ACCEPTED"] && (([HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "t") || ([HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "f"))} { set clientless_mode 0; set form_mode 1 } else { switch -glob [string tolower [HTTP::header "User-Agent"]] { "*microsoft-webdav-miniredir*" { set clientless_mode 1 } "*microsoft data access internet publishing provider*" - "*office protocol discovery*" - "*microsoft office*" - "*non-browser*" - "msoffice 12*" { set form_mode 1 } "*mozilla/4.0 (compatible; ms frontpage*" { if { [ string range [getfield [string tolower [HTTP::header "User-Agent"]] "MS FrontPage " 2] 0 1] > 12 } { set form_mode 1 } else { set clientless_mode 1 } } "*mozilla*" - "*opera*" { set clientless_mode 0 } default { set clientless_mode 1 } } } if { $clientless_mode || $form_mode } { if { [HTTP::cookie exists "MRHSession"] } {set apmstatus [ACCESS::session exists -state_allow $apmsessionid]} else {set apmstatus 0} if { !($apmstatus) && [HTTP::cookie exists $static::ckname] } {set apmpersiststatus [ACCESS::session exists -state_allow $persist_cookie]} else {set apmpersiststatus 0} if { ($apmpersiststatus) && !($apmstatus) } { # Add MRHSession cookie for non browser user-agent first request and persistent cookie present if { [catch {HTTP::cookie insert name "MRHSession" value $persist_cookie} ] } {log local0. "[IP::client_addr]:[TCP::client_port] : TCL error on HTTP cookie insert MRHSession : URL : [HTTP::host][HTTP::path] - Headers : [HTTP::request]"} else {return} } } else { return } if { $clientless_mode && !($apmstatus)} { if { !([HTTP::header Authorization] == "") } { set clientless(insert_mode) 1 set clientless(username) [ string tolower [HTTP::username] ] set clientless(password) [HTTP::password] binary scan [md5 "$clientless(password)"] H* clientless(hash) set user_key "$clientless(username).$clientless(hash)" set clientless(cookie_list) [ ACCESS::user getsid $user_key ] if { [ llength $clientless(cookie_list) ] != 0 } { set clientless(cookie) [ ACCESS::user getkey [ lindex $clientless(cookie_list) 0 ] ] if { $clientless(cookie) != "" } { HTTP::cookie insert name MRHSession value $clientless(cookie) set clientless(insert_mode) 0 } } if { $clientless(insert_mode) } { HTTP::header insert "clientless-mode" 1 HTTP::header insert "username" $clientless(username) HTTP::header insert "password" $clientless(password) } unset clientless } else { HTTP::respond 401 WWW-Authenticate "Basic realm=\"$static::Basic_Realm_Text\"" Set-Cookie "MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/" Connection close return } } elseif {$form_mode && !($apmstatus) && !([HTTP::path] equals $static::MSOFBA(ReqVal))}{ HTTP::respond 403 -version "1.1" noserver \ $static::MSOFBA(ReqHeader) "https://[HTTP::host]$static::MSOFBA(ReqVal)" \ $static::MSOFBA(ReturnHeader) "https://[HTTP::host]$static::MSOFBA(ReturnVal)" \ $static::MSOFBA(SizeHeader) $static::MSOFBA(SizeVal) \ "Connection" "Close" return } } when HTTP_RESPONSE { # Insert persistent cookie for html content type and private session if { [HTTP::header "Content-Type" ] contains "text/html" } { HTTP::cookie remove $static::ckname HTTP::cookie insert name $static::ckname value $apmsessionid path "/" HTTP::cookie expires $static::ckname 120 relative HTTP::cookie secure $static::ckname enable } # Insert session cookie if session was recovered from persistent cookie if { ([info exists "apmpersiststatus"]) && ($apmpersiststatus) } { HTTP::cookie insert name MRHSession value $persist_cookie path "/" HTTP::cookie secure MRHSession enable } } when ACCESS_SESSION_STARTED { if {([info exists "clientless_mode"])} { ACCESS::session data set session.clientless $clientless_mode } if { [ info exists user_key ] } { ACCESS::session data set "session.user.uuid" $user_key } } when ACCESS_POLICY_COMPLETED { if { ([info exists "clientless_mode"]) && ($clientless_mode) && ([ACCESS::policy result] equals "deny") } { ACCESS::respond 401 noserver WWW-Authenticate "Basic realm=$static::Basic_Realm_Text" Connection close ACCESS::session remove } } when ACCESS_ACL_ALLOWED { switch -glob [string tolower [HTTP::path]] { "/sp-ofba-form" { ACCESS::respond 302 noserver Location "https://[HTTP::host]$static::MSOFBA(ReturnVal)" } "/sp-ofba-completed" { ACCESS::respond 200 content {Authenticated Good Work, you are Authenticated } noserver } "*/signout.aspx" { # Disconnect session and redirect to APM logout Page ACCESS::respond 302 noserver Location "/vdesk/hangup.php3" return } "/_layouts/accessdenied.aspx" { # Disconnect session and redirect to APM Logon Page if {[string tolower [URI::query [HTTP::uri] loginasanotheruser]] equals "true" } { ACCESS::session remove ACCESS::respond 302 noserver Location "/" return } } default { # No Actions } } }
Tested this on version:
11.5Hi Stanislas,
Line 11-13: Personally I would make the setting independent of the APM profile configuration. This would allow to distinct between regular browser and MSOFBA clients (not restricted) and clientless access (Single-IP restricted). Furthermore I would recoment to always use the Single-IP mode for clientless access to reduce the security risks associated with the
syntax.[ACCESS::user getsid $user_key]
Line 51: Okay, I got the intention behind. Maybe you should issue a
at the beginning of theif { [catch {HTTP::payload replace 0 0 {}}] } then { return }
/HTTP_REQUEST
events to cover every write access on already responded HTTP requests/responses.HTTP_RESPONSE
Line 135-137: Also tested on TMOS v12.0 and it has worked like a charm for me.
Additional Note: I've identified a problem within the MSOFBA authentication. If the APM session of an MSOFBA client is removed (e.g. session timeout) the MSOFBA client will not be able to re-authenticate using MSOFBA a second time. Only a fresh persistent cookie will allow him to save pending document changes, which is unfortunately not very intuitive for the end user. I've already developed a mechanism to restore/create a minimalistic APM session on-the-fly based on selected session meta-data (e.g. Username, Language-Settings, SSO settings) utilizing an AES encrypted
session cookie. The code already works like a charm and allows me to deploy strict APM Policy session inactivity timeouts (e.g. 900 seconds), while providing a solid backdoor for long-living MSOFBA client sessions (e.g. 1-2 day(s)) without wasting any APM concurrent user CALs. Will perform additional tests and then post my latest iRule for you...MRHSession_R
Cheers, Kai
- Stanislas_Piro2Cumulonimbus
Hi Kai,
I just tested to kill a session for a MSOFBA authenticated user.
When trying to save the document, Office client (In my test, Excel 2011 for Mac) display a message with a "reconnect" button. when I clicked on it, the document was uploaded on the server (as the user was authenticated on the SAML IdP, I was not prompted for user credentials).
I think this is not a problem but the expected behavior when session expires.
Hi Stanislas,
every Windows based Office client I've tested so far does not perform MSOFBA a second time. It receives the err403 and then just stops to follow the MSOFBA headers... :-(
Cheers, Kai
- Stanislas_Piro2Cumulonimbus
I just tried with Excel 2016 for windows. Excel displays the error message with connection button. when I click, the office browser open then close because of already saml session opened.
The irule used when I try is the one of this thread, not the last one in comments.
Hi Stanislas,
Just tried your iRule and mentioned problem remains.
I'm currently testing with Excel14 (aka. Office 2010), but will test some newer Office versions next week, to get a felling which specific versions are afffected.
Cheers, Kai
- domokos_23867Nimbostratus
Hello,
I have used the irule and hit some limitations. I turned on a lot of logging and I see the following behavior. If I use IE I see the 2 cookies inserted (I have also a VPE that does SAML auth followed by Kerberos SSO and the access policy uses persistent cookies). I click on the link to open the document in WORD and word fires. It always has the MRH cookie but not the _SP one. The variable apmstatus is set to 1 and apmpersiststatus is set to 0. I guess it never tries to insert the _SP one because the MRH cookie is always there. So it seems that word shares the cookie of IE. Now when I use Firefox I see in the FF session the 2 cookies but when word comes up it does not use any of them. So I get a redirect inside WORD to go to the SAML iDP to authenticate. This comes from the fact that the cookie that FF has is not used by word, so I wonder if you have any ideas on how to link the word session to FF. I cannot find anything in common that would link the 2 of them.
Thanks in advance Carol
- Stanislas_Piro2Cumulonimbus
Hi,
Firefox does not write persistent cookies on the same folder than Microsoft products, and I'm not sure the cookie storage format is the same between applications. re-authentication is required when editing document from FF.
in the irule, there is no link between apmpersiststatus and _SP cookie.
- asaf_01_136490Nimbostratus
Hey Kai , can you please publish the final version . another thing , you need to disable the protected mode in IE.
- Daphne_WonRet. Employee
Everyone, I like to engage the folks here that have deployed OFBA iRules to support authentication access from native Office apps to Sharepoint on-prem. Can you email me to see what authentication methods you need, what version of the Office clients you need to support, and what versions of the Sharepoints you have deployed for? You can email me at d.won@.
- ryanm99Nimbostratus
Hello Everyone,
I am attempting to set up the iRule that was shared here to allow Microsoft Office Mobile apps to access SharePoint through APM. I am currently running version 13.0.0. When I attempt to access the environment, I get the following in the APM log file:
May 19 22:34:17 DV-BIGIP01 err tmm1[19118]: 01490514:3: (null):Common:00000000: Access encountered error: ERR_ARG. File: ../modules/hudfilter/access/access.c, Function: access_sanitize_portal_headers, Line: 15499 May 19 22:34:17 DV-BIGIP01 err tmm1[19118]: 01490514:3: (null):Common:00000000: Access encountered error: ERR_ARG. File: ../modules/hudfilter/access/access.c, Function: access_forward_request_to_portal, Line: 15578 May 19 22:34:17 DV-BIGIP01 err tmm1[19118]: 01490514:3: (null):Common:00000000: Access encountered error: ERR_ARG. File: ../modules/hudfilter/access/access.c, Function: access_process_state_client_enforce_session, Line: 7261 May 19 22:34:17 DV-BIGIP01 err tmm1[19118]: 01490514:3: (null):Common:00000000: Access encountered error: ERR_ARG. File: ../modules/hudfilter/access/access.c, Function: hud_access_handler, Line: 2735
Does anyone have any ideas on something I could try? On the mobile app side it just spins at connecting for a while and eventually times out. Thanks!text