RelayState is overwritten by SamlAuthenticationHandler

I’m in the process of setting up SAML on my ASP.NET Core Identity Server 4 project. I’m acting as the Service Provider and have utilized the ExampleIdentityProvider project in the samples folder to act as the IdP.

Everything’s working as I would expect with the exception of the “.redirect” AuthenticationProperty which is constructed inside the HandleSsoAsync method of the SamlAuthenticationHandler.

The source of this method looks something like this:

authenticationProperties2.RedirectUri = this.GetLoginRedirectUrl(!ssoResult.IsInResponseTo ? ssoResult.RelayState : (string) null);
authenticationProperties2.Items[“.redirect”] = authenticationProperties2.RedirectUri;

The result of this is that the HandleSsoAsync method redirects to /Account/ExternalLogin?handler=Callback and the .redirect claim is assigned the same value. Essentially, I’ve lost access to the original RedirectUrl that came through in the RelayState (/api/values).

Does anyone have any pointers on this. It looks like a bug but I suspect its something I’m missing about the SP-init SSO flow with SAML.

Hi Matt
Is this IdP-initiated or SP-initiated SSO?

[quote]
ComponentSpace - 8/29/2018
Hi Matt
Is this IdP-initiated or SP-initiated SSO?
[/quote]

SP-init.

Sorry, it was buried in the last line - I should have called that out at the start ;)

Sorry, I missed that.
As part of SP-initiated SSO, the RedirectUri is saved at the time of the challenge.
When the SAML response is received, we check for this saved RedirectUri.
If none is present and this is IdP-initiated SSO, the code you described is executed to pick up the URI from the relay state, if any.
For SP-initiated SSO, the relay state shouldn’t be used. Instead, the RedirectUri from the challenge is used.
I believe that redirecting to /Account/ExternalLogin?handler=Callback is correct.
Where did you get the relay state (/api/values) you mentioned?

[quote]
ComponentSpace - 8/29/2018
Sorry, I missed that.
As part of SP-initiated SSO, the RedirectUri is saved at the time of the challenge.
When the SAML response is received, we check for this saved RedirectUri.
If none is present and this is IdP-initiated SSO, the code you described is executed to pick up the URI from the relay state, if any.
For SP-initiated SSO, the relay state shouldn't be used. Instead, the RedirectUri from the challenge is used.
I believe that redirecting to /Account/ExternalLogin?handler=Callback is correct.
Where did you get the relay state (/api/values) you mentioned?

[/quote]

If I intercept the request to /SAML/AssertionConsumerService (through an app.Use call in Startup.cs), I can call ReceiveSsoAsync and inspect the SsoResult which has a RelayState of /api/values. api/values is the original endpoint that initiated the Challenge request.

The flow is:
(SP) http://localhost:5000/api/values
(IdP) https://localhost:44313/SAML/SingleSignOnService
(SP) http://localhost:5000/SAML/AssertionConsumerService (inspection of the SsoResult here shows RelayState: /api/values)
(SP) http://localhost:5000/Account/ExternalLogin?handler=Callback

I think what I'd like to do is continue the flow back to based on an AuthenticationProperty that is still available from the context of the ExternalLogin page.

(SP) http://localhost:5000/api/values

I'm currently trying to inspect the values in the ISsoSessionStore at various points to understand how this works. But it seems like this should "just work" without all the hacking.
Am I doing something fundamentally wrong here?

Please enable SAML trace at the SP and send the generated log file as an email attachment to support@componentspace.com, also mentioning your forum post.
https://www.componentspace.com/Forums/7936/Enabling-SAML-Trace