APM Full Step Up Authentication
Problem this snippet solves:
By default, APM is not able to handle several authentication during a session. Once you are logged in, it’s finished, you can’t ask for authentication again.
Since v12.1.0, we can see a new feature in EA called “Step-up Authentication” and the introduction of subroutines that is currently limited to ldap authentication or a confirm box.
The irule and configuration below allow the administrator to define 2 levels of authentication based on URIs. The concept can be extended to have multiple authentication levels.
This concept can be extended to define several Level of authentication. You can also change the element that trigger the additionnal authentication process.
How to use this snippet:
Installation
irule
To make it works, you need to install the irule on the Virtual Server that publish your application with APM authentication.
datagroup
You need to create a datagroup of string type. This dg must contains http path that need an additional authentication step. The dg is named loa3_uri in the irule example.
access profile
If you already have an existing access profile, you will need to modify it and include some additionnal configuration in your VPE. If you have no access profile, you can starts building your own based on the description we provide below.
Scenarios
1) User try to reach strong uri after first authentication process
In this scenario, the user first authenticate using a standard authentication mecanism. Once authenticated, if the user request content that is behing strong uris, the user restart an authentication process in the "Strong Auth" and "Already Auth" branch of the VPE.
2) User try to reach strong uri during the first authentication process
If the user try to access a strong uri on its first attempt, he will need to complete the full authentication process. Then, he can access every part of the web application without any additional prompt.
Special considerations
Client certificate Authentication
You may need to use Client certificate authentication as a primary factor or second factor. We highly recommend to use "SSl on-demand authentication" if you need it as primary factor. Client Certificate is not supported as a second factor, you need to use SSl on-demand authentication.
WebSSO
When first authentication has already been allowed and the user try to access a protected uri, the system will invite the user to complete the new authentication (second factor). This process will restart a webSSO action on the backend. Basic, NTLM and Kerberos webSSO have been tested with success.
Configuring the Visual Policy Editor
The printscreen below is a minimal Visual Policy Editor used to make Step up Authentication works properly :
Strong Auth
The strong Auth block is an "Empty Action" with two branch.
The branch named "Strong" contains the following condition :
expr { [mcget {session.server.landinguri}] starts_with "/strong" || [mcget {session.custom.last.strong}] == 1 }
We check that the uri starts with strong (used in scenario 1) or if a custom variable is set to 1 (second scenario)
Already Auth
This is an empty action with two branch. The branch named "yes" contains the following expression :
expr { [mcget {session.custom.last.authresult}] contains "true" }
2-factor Ending
session.custom.last.authtype
variable must be set to 1
session.policy.result.redirect.url
must be changed. The session.server.landinguri
contains the true origin uri.
To set this variable, you must use the tcl script below :
proc urldecode str { variable map variable alphanumeric a-zA-Z0-9 for {set i 0} {$i <= 256} {incr i} { set c [format %c $i] if {![string match \[$alphanumeric\] $c]} { set map($c) %[format %.2x $i] } } array set map { " " + \n %0d%0a } set str [string map [list + { } "\\" "\\\\"] $str] regsub -all -- {%([A-Fa-f0-9][A-Fa-f0-9])} $str {\\u00\1} str return [subst -novar -nocommand $str] } set decoded_uri [urldecode [string range [mcget {session.server.landinguri}] [expr { [string last = [mcget {session.server.landinguri}]] + 1 }] end]] return $decoded_uri
Full strong Ending
session.custom.last.authtype
variable must be set to 1
Standard Ending
session.custom.last.authtype
variable must be set to 0
Session variables
The following variables can be used in the 2-factor section of the Visual Policy Editor :
- session.custom.last.username
- session.custom.last.password
Features
- 2-step authentication
- Retrieve username and password from first authentication
- Encrypt Session1 cookie to avoid session Hijacking
External links
Github : https://github.com/e-XpertSolutions/f5
Code :
when RULE_INIT { # to be changed prior to any publishing set passphrase "hEuoYjmFUpB4PcpO3bUdQtLP4ic7jjm" } when HTTP_REQUEST { if { [HTTP::cookie exists MRHSession] and [ACCESS::session exists -state_allow -sid [HTTP::cookie MRHSession]] } { set strong_auth [ACCESS::session data get session.custom.last.authtype] if { [class match [HTTP::path] starts_with loa3_uri] and $strong_auth == 0 } { HTTP::cookie encrypt "MRHSession" $passphrase HTTP::respond 302 noserver "Location" "/strong?return_url=[URI::encode [HTTP::uri]]" "Cache-Control" "no-cache, must-revalidate" Set-Cookie "MRHSession=deleted;expires=Thu, 01-Jan-1970 00:00:10 GMT;path=/" Set-Cookie "LastMRH_Session=deleted;expires=Thu, 01-Jan-1970 00:00:10 GMT;path=/" Set-Cookie "Session1=[HTTP::cookie MRHSession];path=/" } } } when ACCESS_SESSION_STARTED { # decrypt Session1 cookie value set decrypted [HTTP::cookie decrypt "Session1" $passphrase] if { [HTTP::cookie exists Session1] and [ACCESS::session exists -state_allow -sid $decrypted] } { ## section : retrieve session variables from the first session ACCESS::session data set session.custom.last.username [ACCESS::session data get session.logon.last.username -sid $decrypted] ACCESS::session data set session.custom.last.password [ACCESS::session data get session.logon.last.password -sid $decrypted] ## End section ACCESS::session data set session.custom.last.authresult "true" # remove the first created session during standard authentication to avoid multiple active sessions ACCESS::session remove -sid $decrypted } elseif { [class match [HTTP::path] starts_with loa3_uri] } { ACCESS::session data set session.custom.last.strong 1 } }
Tested this on version:
11.5- JRahmAdminif you add "programmability contest" as a tag, you'll be entered into our Codeshare Challenge contest we're running this month!
- Peter_Baumann_5NimbostratusVery well done, congratulations! Does someone have an example to implement the above but with SAML, preferred with Auth Context Class which is in the SAML 2.0 Standard?
Hi Yann, thanks for sharing this! I'am trying to implement this on a 12.1.1 HF1 platform and I ran into some problems. The session cookies didn't get deleted when the browser was redirected to /strong. So it ran into a loop. I had to alter the irule and add the domain setting to the cookies.
HTTP::respond 302 noserver "Location" "/strong?return_url=[URI::encode [HTTP::uri]]" "Cache-Control" "no-cache, must-revalidate" Set-Cookie "MRHSession=deleted;expires=Thu, 01-Jan-1970 00:00:10 GMT; domain=example.com;path=/" Set-Cookie "LastMRH_Session=deleted;expires=Thu, 01-Jan-1970 00:00:10 GMT; domain=example.com;path=/" Set-Cookie "Session1=[HTTP::cookie MRHSession]; domain=example.com;path=/"
Thanks again for sharing this. Best regards, Niels
Hi Niels,
Thank you for your comment. The behavior you get is basically independant from the BIG-IP version. In fact, when you specify a domain or host in the Multi-Domain SSO tab in your access policy, the BIG-IP generate MRHSession cookies including the domain you specified. In that case, you effectively need to change the line of code you highlight.
Thank you for your remark, I will add a note on that in the main post.
Best Regards
Yann
Hi Yann,
Thanks for clarifying that. I found another thing in the iRule that doesn't seem to work for me. It has to do with the password setting between the old and the new session. This rule (line 26):
ACCESS::session data set session.custom.last.password [ACCESS::session data get session.logon.last.password -sid $decrypted]
The APM logon password variable isn't accessible from an iRule. I had to implement a workaround as described here:
Best regards,
Niels
- Stanislas_Piro2Cumulonimbus
Hi Yann,
Nice Job! I found another way for step-up authentication using ACL instead of Datagroup.
when CLIENT_ACCEPTED { retreive ACCESS Profile cookie settings to restore in cookie changes. if {[PROFILE::access domain_mode] equals 0 } { if { [set cookieproperties ";domain=[PROFILE::access domain_cookie]"] equals ";domain="} {set cookieproperties ""} if { [PROFILE::access secure_cookie]} {append cookieproperties ";secure"} if { [PROFILE::access httponly_cookie]} {append cookieproperties ";httponly"} } } when HTTP_REQUEST { If user fails to authenticate in strong auth, restore the previous session cookie. if { [HTTP::cookie exists AuthRollBack] & [HTTP::cookie exists Session1] } { HTTP::respond 302 noserver "Location" "[HTTP::uri]" "Cache-Control" "no-cache, must-revalidate" Set-Cookie "MRHSession=[HTTP::cookie Session1]$cookieproperties;path=/" Set-Cookie "LastMRH_Session=deleted$cookieproperties;expires=Thu, 01-Jan-1970 00:00:10 GMT;path=/" Set-Cookie "Session1=deleted$cookieproperties;expires=Thu, 01-Jan-1970 00:00:10 GMT;path=/" Set-Cookie "AuthRollBack=deleted;expires=Thu, 01-Jan-1970 00:00:10 GMT;path=/" } set referer [HTTP::header value Referer] } when ACCESS_ACL_DENIED { If User is denied by ACL and authnetication level is not strong, force to create a new session for strong authentication. if {[ACCESS::session data get session.custom.last.strong] == 0 } { ACCESS::respond 302 noserver "Location" "[HTTP::uri]" "Cache-Control" "no-cache, must-revalidate" Set-Cookie "MRHSession=deleted$cookieproperties;expires=Thu, 01-Jan-1970 00:00:10 GMT;path=/" Set-Cookie "LastMRH_Session=deleted$cookieproperties;expires=Thu, 01-Jan-1970 00:00:10 GMT;path=/" Set-Cookie "Session1=[HTTP::cookie MRHSession]$cookieproperties;path=/" ACCESS::session data set session.custom.last.Referer $referer } } when ACCESS_SESSION_STARTED { If new session and previous session is store in a cookie, prepare policy for strong authentication if { [HTTP::cookie exists Session1] and [ACCESS::session exists -state_allow -sid [set sessionid [HTTP::cookie value "Session1"]]] } { section : retrieve session variables from the first session ACCESS::session data set session.custom.last.username [ACCESS::session data get session.logon.last.username -sid $sessionid] ACCESS::session data set session.custom.last.password [ACCESS::session data get session.logon.last.password -sid $sessionid] End section store the first created session during standard authentication to avoid multiple active sessions ACCESS::session data set session.custom.last.session $sessionid ACCESS::session data set session.custom.last.strong 1 } else { ACCESS::session data set session.custom.last.strong 0 } } when ACCESS_POLICY_COMPLETED { If Strong authentication process if {[ACCESS::session data get session.custom.last.strong]} { if { ([ACCESS::policy result] equals "deny") } { restore previous session if the user failed to authenticate. ACCESS::session remove ACCESS::respond 302 noserver "Location" [ACCESS::session data get session.custom.last.Referer -sid [ACCESS::session data get session.custom.last.session]] "Cache-Control" "no-cache, must-revalidate" Set-Cookie "AuthRollBack=1;path=/" } else { Remove the first created session during standard authentication to avoid multiple active sessions ACCESS::session remove -sid [ACCESS::session data get session.custom.last.session] } } }
The VPE is quite simple (I removed authentication boxes to test... it is simpler)!
In Strong Auth, I set the branch expression
expr { [mcget {session.custom.last.strong}] == 1 }
With this solution, you can assign different ACL based on AD group and manage step-up per user and not a global URL list.
If a user fails to authenticate on strong authentication, he is redirected to the referer stored in a session variable of the previous session.
This irule also manage the access profile cookie parameters (Secure, httponly, domain).
I removed the cookie encryption as F5 does not encrypt the default session cookie and I preserve cookie security parameters for session1 cookie.
This solution does not manage 2 way to authenticate (standard + strong / standard then strong) but always the same process : standard then strong.
- Marcos_Penha_16Nimbostratus
do you have the configuration in text mode ? it's not really clear for me what is configured on the boxes "set redirect url"
regards,