Forum Discussion

amass87_221296's avatar
amass87_221296
Icon for Nimbostratus rankNimbostratus
Jan 10, 2017

Fixing Incomplete SAML SP Initiated Login

This is not really a question, because I already know the answer. I spent a fair amount of time and received awesome help from a few people on this forum. I wanted to post this here so others can avoid the same headache.

Specific Issue: Service Provider sends what they call a "Partial SP Initiated Authentication." What really happens is that they perform a 302 Location redirect and have both SAMLRequest and RelayState parameters in the URL. However, SAMLRequest= is blank. They have neglected to deflate, 64-bit encode, and URL encode a SAML Request in their redirect.

Fixing Missing SAML Request: Since the Service Provider is not sending a SAMLRequest, the F5 has to trigger an IdP initiated login, and this can be done with an iRule attached to the webtop Virtual Server. However, this will only get you connected to the landing page and does not take into account the RelayState parameter sent in the 302 Location redirect.

Fixing the RelayState: The way this was accomplished was by creating back-to-back virtual servers, using cookies to pass the appropriate RelayState URI, and a Stream profile to modify the SAML Response on its way back to the user's web browser.

Front-end Virtual Server: The front-end virtual server has 2 responsibilities. The first is to forward all traffic through from the user's web browser on to the webtop virtual server. This is a simple iRule. The second responsibility is to use a Stream profile to modify the SAML Response and append the missing RelayState information appropriately.

Back-end Virtual Server: The back-end virtual server is for hosting the Access Policy and an iRule that catches the request, initiates an unsolicited IdP SAML Response, and passes the RelayState back to the front-end virtual server via a http cookie.

Note: I took a shortcut on setting up the RelayState form element by pre-populating the SP connector with an "/" in the RelayState field.

Front-end iRule to redirect all traffic to back-end virtual server:

when HTTP_REQUEST {

    virtual /Common/VS_Portal

    log local0. "Forwarded to Portal"

}

Back-end iRule to initiate SAML Response and pass RelayState via cookie:

when ACCESS_POLICY_COMPLETED {

if { [string tolower [ACCESS::session data get session.server.landinguri]] contains "apps" } {

    if { [ACCESS::session data get session.server.landinguri] == "/saml/idp/profile/redirectorpost/sso" } { 

        log local0. "SP initiated SAML detected, not sending redirect" 

    } else {

        set relaystatevalue "[string map {"%2f" "/" "%3f" "?" "%3d" "="}[URI::query [ACCESS::session data get session.server.landinguri] "RelayState"]]"

        ACCESS::respond 302 Location "https://go.domain.com/saml/idp/res?id=/Common/SAML_APP"

        log local0. "IDP initiated SAML detected, sending redirect [URI::query [ACCESS::session data get session.server.landinguri] "RelayState"]"

        HTTP::cookie insert name "RelayState" value $relaystatevalue domain ".domain.com"

        return

    }

}
`


}

**Front-end iRule to modify return traffic SAML Response and modify RelayState:**

when HTTP_REQUEST {

`set relaystatesetter 0

set relaystatevalue 0

set relaystateexists 0

if {[HTTP::cookie exists "RelayState"]}{

    set relaystateexists 1

    set relaystatevalue "[HTTP::cookie RelayState]"

}

log local0. "iRule Logger - HTTP_REQUEST Starting hostname=[HTTP::host];uri=[HTTP::uri]"

if {[HTTP::uri] contains "RelayState"}{

    log local0. "iRule Logger - HTTP_REQUEST RelayState Store Cookie hostname=[HTTP::host];uri=[HTTP::uri]"

    set relaystatesetter 1

set relaystatevalue "[string map {"%2f" "/" "%3f" "?" "%3d" "="}[URI::query [HTTP::uri] RelayState]]"

    log local0. "iRule Logger - RelayState is $relaystatevalue;relaystatesetter=$relaystatesetter"

    HTTP::cookie insert name "RelayState" value $relaystatevalue domain ".domain.com"

}
`

}

when HTTP_RESPONSE {

`    if {$relaystatesetter==1}{ HTTP::cookie insert name "RelayState" value $relaystatevalue domain ".domain.com" }

    log local0. "iRule Logger - HTTP_RESPONSE Triggered - relaystate=$relaystatevalue"

    if {$relaystateexists==1}{

        log local0. "iRule Logger - HTTP_RESPONSE Triggered - relaystateexists=$relaystateexists"

        STREAM::expression "@@@"

        STREAM::enable

    }

}