Skip to content

Instantly share code, notes, and snippets.

@AlainODea
Last active June 10, 2023 21:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AlainODea/d92edeab5edc9eaa4be83fa4cf540267 to your computer and use it in GitHub Desktop.
Save AlainODea/d92edeab5edc9eaa4be83fa4cf540267 to your computer and use it in GitHub Desktop.
Okta ACS RelayState issue exploration

An example with minimal dependencies is a loopback within an Okta Org.

Say you want to get to the admin interface of an Org. It's at:

/home/admin-entry

URL encoded (what you need for RelayState) that is:

%2Fhome%2Fadmin-entry

You can verify this by looking at the URL of the Admin link on the /app/UserHome page.

You create a Identity Provider called loopback in Okta. My example has an ACS URL of:

https://dev-971545.oktapreview.com/auth/saml20/loopback

You create an App that is linked to that Identity Provider called loopback. It has a Single Sign-on URL (visible after clicking View Setup Instructions on the Sign-on tab) of:

https://dev-971545.oktapreview.com/app/independentconsultantdev927755_loopback_1/exkadbfail8okn4W80h7/sso/saml

There are two layers of RelayState:

  1. The app you want Okta to direct you to: in this case the loopback app
  2. The path within that app

Passing the RelayState to an App SSO URL directly as a URL query parameter works according to SAML specification:

https://dev-971545.oktapreview.com/app/independentconsultantdev927755_loopback_1/exkadbfail8okn4W80h7/sso/saml?RelayState=%2Fhome%2Fadmin-entry

The Okta App SSO URL responds with an HTML form with a RelayState value of /home/admin-entry HTML entity encoded as /home/admin-entry. This is correct and SAML specification compliant behavior.

Following that URL brings me to the Admin dashboard.

There's no inbound SAML yet. This is all within Okta. Let's add the SAML layer.

The path of that App SSO URL is:

/app/independentconsultantdev927755_loopback_1/exkadbfail8okn4W80h7/sso/saml?RelayState=%2Fhome%2Fadmin-entry

We URL encode that to prepare it to be the value of a query parameter:

%2Fapp%2Findependentconsultantdev927755_loopback_1%2Fexkadbfail8okn4W80h7%2Fsso%2Fsaml%3FRelayState%3D%252Fhome%252Fadmin-entry

You then drop that on the ACS of the loopback Identity Provider as the RelayState query parameter:

https://dev-971545.oktapreview.com/auth/saml20/loopback?RelayState=%2Fapp%2Findependentconsultantdev927755_loopback_1%2Fexkadbfail8okn4W80h7%2Fsso%2Fsaml%3FRelayState%3D%252Fhome%252Fadmin-entry

The Okta ACS URL responds with an HTML form with a RelayState value of ?RelayState=/app/independentconsultantdev927755_loopback_1/exkadbfail8okn4W80h7/sso/saml?RelayState=%2Fhome%2Fadmin-entry HTML entity encoded as %3FRelayState%3D%2Fapp%2Findependentconsultantdev927755_loopback_1%2Fexkadbfail8okn4W80h7%2Fsso%2Fsaml%3FRelayState%3D%252Fhome%252Fadmin-entry. This is INCORRECT and violates the SAML 2.0 specification.

An HTTPS request to an Okta ACS URL with a RelayState query parameter of ActualRelayState yields an HTTPS response with an HTML form with a RelayState value equal to ?RelayState=ActualRelayState. It literally prepends ?RelayState=. It should respond with an HTML form with a RelayState value equal to ActualRelayState. To clarify, this is INCORRECT and violates the SAML 2.0 specification.

In our case, the ACS with RelayState generates a form which POSTS to Okta which logs you in and then redirects to:

?RelayState=/app/independentconsultantdev927755_loopback_1/exkadbfail8okn4W80h7/sso/saml?RelayState=%2Fhome%2Fadmin-entry

That's completely useless and won't work. Ideally the Okta ACS would write the HTML form with the literal RelayState instead.

So instead, we strip off the leading slash (%2F in the encoded version) and use a fromURI query parameter on the Okta ACS instead of using RelayState:

https://dev-971545.oktapreview.com/auth/saml20/loopback?fromURI=app%2Findependentconsultantdev927755_loopback_1%2Fexkadbfail8okn4W80h7%2Fsso%2Fsaml%3FRelayState%3D%252Fhome%252Fadmin-entry

You can convince yourself that worked or you can open the Network tab of your browser's developer tools and trace the communications. The ACS drops a RelayState prepending the slash (that's why we had to remove it), then gets redirected to the IdP (the loopback App), then gets reflected back to the ACS (preserving the RelayState). Login completes and Okta redirects to the loopback App SSO URL with the RelayState in the query parameter. Okta processes that and redirects to itself and then redirects to the embedded RelayState which takes you to the Admin Dashboard.

This is a working example of Deep Linking from SP-initiated flow from Okta with Okta as a SAML intermediary to another app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment