Problem/Motivation
The redirects for the /saml/login and /saml/logout routes (samlauth.saml_controller_login, samlauth.saml_controller_logout) are apparently cached by Drupal.
The first login after the cache is cleared works as desired. Then, Drupal seems to cache the relay state redirect. In our case, the RelayState url is always behind the login, and we have code that automatically redirects pages that require authentication to samlauth.saml_controller_login. Therefore, we get into a redirect loop between our Relay State url and /saml/login.
Upon successful logout, we redirect to our homepage. The first logout (after the cache is cleared) works perfectly. Thereafter, requests for /saml/logout were redirecting to the homepage without hitting SamlController.
Proposed resolution
I got our system running perfectly all the time by adding the no_cache: TRUE option to both of the aforementioned routes.
Comments
Comment #2
roderikThe redirect responses to /saml/login and /saml/logout are indeed cached, since the RC1 version, and you can turn that off by setting the "Cache HTTP responses containing metadata" (metadata_cache_http) configuration value to 0. (The default is 600 seconds for newly installed sites; on existing sites where the configuration value didn't exist before, the value stays 0.)
But... I don't understand your situation. I cannot reproduce it, regardless whether these responses are cached.
What is likely happening:
/saml/login doesn't redirect to the RelayState url. It redirects to the IdP.
The route that redirects to the RelayState is /saml/acs, which is never cached.
So
1) Some details seem to be off, in the issue report;
2) i suspect that the redirect loop you are seeing, is:
/saml/login -> the external IdP -> (POST to) /saml/acs -> your RelayState -> /saml/login -> the external IdP -> (POST to) /saml/acs -> RelayState -> etc
If you want to, you can check that with e.g. a "HTTP headers" browser extension.
What I cannot reproduce:
After /saml/acs is executed, the user should be logged in. So subsequent requests (redirected to your RelayState) should never be hitting the page cache - for either your RelayState or /saml/login - regardless whether the response is cached. The 'simple' page cache is only used for anonymous users.
(And for logged-in users, /saml/login internally generates a HttpAccessDeniedException - which is caught, and the user is redirected to their profile page. So that would break any redirect loop. Also, /saml/acs should never redirect back to /saml/login.)
The question is: Why are your users still hitting the page cache after having passed through /saml/acs?
(
For reference: the page cache recognizes "anonymous users" by the absence of this session cookie - at least by default:
I've spent some time debugging this, because initially I thought that I could reproduce your situation. However I cannot.
The only situation where I can recreate an eternal redirect loop, is if the domain name used for the initial /saml/login link is different from the domain name which the IdP sends you back to (on /saml/acs). But that happens independently of whether the /saml/login request is cached.
)
Comment #3
richard.thomas commentedI'm currently seeing a similar infinite redirect loop after updating to 8.x-3.0-rc1, although I'm having real trouble reproducing it in a reliable way. I have a feeling it's related to having a destination parameter set when sending the user to /saml/login. It also may somehow involve the dynamic page cache somehow.
Once the bug has occurred I'm seeing this:
As a logged out user, any request to /saml/login?destination=/blah seems to hit the dynamic page cache (based on the returned HTTP headers) on the first request and be sent directly back to the Drupal destination rather than being redirected to the IdP.
If the user was redirected by r4032login, it ends up sending the user back to the original page they couldn't access, which then sends them back to /saml/login in a loop.
Don't have a fix yet, I tried turning off metadata_cache_http and setting requests_cache_http_secs to 0, but it doesn't seem to have fixed it.
Comment #4
richard.thomas commentedOK I think I may have figured out the issue, when the Saml login controller runs it checks the destination parameter, then puts it into the SAML request, then removes the parameter from the request:
In SamlController->getUrlFromDestination():
$request_query_parameters->remove('destination');
This means that RedirectResponseSubscriber leaves the response alone.
However if this response gets cached in the dynamic page cache, then on the next request, the controller code doesn't run and the destination parameter stays in the request object. That means the RedirectResponseSubscriber replaces the redirect to the IdP with a redirect back to Drupal.
I'm not too sure how best to fix this though, other than preventing any caching of these responses in the first place.
Comment #6
bmelvin1 commentedThanks for looking into this. I use SAML Chrome Panel to log the sequence of redirects. Somehow, the redirect to the internal Drupal page (the destination parameter passed to saml/login) is cached as the response to saml/login. Logically, one would think Drupal would cache the redirect to the IdP. I can't even imagine how it's doing what it's doing, but the evidence is clear. There is a similar problem on logout. I'm probably missing something, but I can't yet see why it is necessary - or even desirable - to cache the response to saml/login and saml/logout. With no_cache: TRUE, all the problems go away.
Comment #7
roderikOh, many thanks for that analysis!
I should weigh our options: Either add our own response subscriber that is executed before RedirectResponseSubscriber and strips the 'destination' parameter... or implement our own kind of caching. (Of at least the SAML message including the computed hash - based on the input 'destination' parameter.)
Probably the former. But not before releasing 3.0. There wasn't an overwhelming practical reason to implement this caching right now - it just felt good to cache the result of an expensive computation.
Backing out that change for now.
Comment #8
roderikCreating https://www.drupal.org/project/samlauth/releases/8.x-3.0-rc2 .