SAML Middleware - Relaystate missing on redirection

I am trying to implement an asp.net core 2.1 webapi proof-of-concept using the ComponentSpace SAML Middleware for SP initiated SSO authentication:
// Add SAML SSO services.
services.AddSaml(configuration.GetSection(“SAML”));

// Add SAML authentication services.
services.AddAuthentication().AddSaml(options =>
{
options.PartnerName = () => configuration[“PartnerName”];
});

The authentication seems to work so far: A service method is called in our webapi which in turn redirects to the IDP. The IDP then calls the AssertConsumerService method of the webapi. So far so good. Unfortunately when I look at the received ssoResult, the RelayState is always null. I have traced the http calls and can see that the relaystate is not included in the 302 location result (only the SAML request variable).

I guess the RelayState is used to redirect the calling client to the actual URL after the AssertionConsumerServer method completed successfully. Do I miss some sort of configuration? Why is the RelayState not set automatically?

I appreciate your help.
Greetings
Armin




Hi Armin
Your understanding of the use of the relay state is correct.
If the SAML authentication handler is supplied with a return URL, it saves this as the relay state and will redirect to this URL once SSO completes.
If no URL is supplied, it redirects to a configured URL.
It’s possible no return URL is being supplied.
Please enable SAML trace and send the generated log file as an email attachment to support@componentspace.com mentioning your forum post.
https://www.componentspace.com/Forums/7936/Enabling-SAML-Trace

[quote]
ComponentSpace - 11/19/2018
Hi Armin
Your understanding of the use of the relay state is correct.
If the SAML authentication handler is supplied with a return URL, it saves this as the relay state and will redirect to this URL once SSO completes.
If no URL is supplied, it redirects to a configured URL.
It's possible no return URL is being supplied.
Please enable SAML trace and send the generated log file as an email attachment to support@componentspace.com mentioning your forum post.
https://www.componentspace.com/Forums/7936/Enabling-SAML-Trace
[/quote]

I will gladly provide you with the generated log files, but could you tell me first how I would supply a return URL to the SAML authentication handler using the middleware in the first place? I can't find anything about the configuration of the Relay State in your documentation. Thanks a lot!

This should be handled by the Microsoft Identity pages.
If you take a look at the /Account/ExternalLogin.cshtml.cs under the Identity scaffold, it includes something similar to the following.

public IActionResult OnPost(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Page(“./ExternalLogin”, pageHandler: “Callback”, values: new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}


This instigates a challenge call to the authentication handler passing the redirectUrl in the AuthenticationProperties.
We then save the redirectUrl from the AuthenticationProperties as the relay state so that we can redirect to this URL once SSO completes.
Typically the URL we redirect to is back to /Account/ExternalLogin so it can complete the process.

[quote]
ComponentSpace - 11/20/2018
This should be handled by the Microsoft Identity pages.
If you take a look at the /Account/ExternalLogin.cshtml.cs under the Identity scaffold, it includes something similar to the following.

public IActionResult OnPost(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}


This instigates a challenge call to the authentication handler passing the redirectUrl in the AuthenticationProperties.
We then save the redirectUrl from the AuthenticationProperties as the relay state so that we can redirect to this URL once SSO completes.
Typically the URL we redirect to is back to /Account/ExternalLogin so it can complete the process.
[/quote]

Well I guess the code you have posted is used by web pages utilizing a user login mechanism. My scenario is sligthly different though. We want to implement a token based authentication using a Kerberos token in a WEBAPI. On each call to the WebApi the the user must be authentication using the Authorize Attribute. After an successful authorization the user should be forwarded to the resource he actually wanted to access.
Shouldn't the middleware take care of this and automatically append the queried url as relay state to the 302 query? Do you have a sample project of a SP provider used by a WEBAPI? The sample project you are offering is based on web pages which do have a different authentication flow.

From the log:
2018-11-20 14:41:00.037 +01:00 [Debug] The SAML SSO environment has been successfully initialized.
2018-11-20 14:41:00.369 +01:00 [Debug] The SAML authentication handler doesn't support HandleAuthenticateAsync.
...
2018-11-20 14:41:00.372 +01:00 [Information] "SAML" was not authenticated. Failure message: "The SAML authentication handler doesn't support this form of authentication."
2018-11-20 14:41:00.415 +01:00 [Debug] The SAML authentication handler is initiating SSO as part of an authentication challenge.
2018-11-20 14:41:00.420 +01:00 [Debug] A redirect URL hasn't been specified.

I can also see this weird message:
2018-11-20 16:07:42.705 +01:00 [Information] "SAML" was not authenticated. Failure message: "The SAML authentication handler doesn't support this form of authentication."

The middleware works in concert with Microsoft identity.
It doesn’t automatically append URLs etc.
SAML SSO is a browser based protocol.
Messages between the IDP and SP sites are sent via the browser rather than directly.
It’s possible to support SSO in a web API application but SSO shouldn’t be part of a web API.
We have an example of an Angular application and web API supporting SSO which will be published with the next release.
Please email us if you’d like a copy.

[quote]
ComponentSpace - 11/20/2018
This should be handled by the Microsoft Identity pages.
If you take a look at the /Account/ExternalLogin.cshtml.cs under the Identity scaffold, it includes something similar to the following.

public IActionResult OnPost(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}


This instigates a challenge call to the authentication handler passing the redirectUrl in the AuthenticationProperties.
We then save the redirectUrl from the AuthenticationProperties as the relay state so that we can redirect to this URL once SSO completes.
Typically the URL we redirect to is back to /Account/ExternalLogin so it can complete the process.
[/quote]

With the version I have (3.0.0) I have the same issue in the SP initiated sign in.

The issue appears to be that the SamlAuthenticationHandler does not supply any relayState, nor the SSOOptions, to the ISamlServiceProvider's InitiateSsoAsync method. The relay state is always null.

If you want relay state, you have to wrap the real SamlServiceProvider with your own ISamlServiceProvider and delegate after setting the relaystate.
If you want to use the AuthenticationProperties from the Challenge, then it get's complicated. The only place it gets stored before calling the SamlServiceProvider is the SSOSessionStore. If you are using a cookie based store, then you can't retrieve the new entry, it's writing directly to the response cookies. Again, wrap the real implementation and store value from the SaveAsync method so you can retrieve it in the same request.

ComponentSpace.. please consider allowing us to supply relaystate and SSOOptions for SP initiated challenges without having to wrap all the implementations, either via events, options, or via other injectable factories.


We could add the relay state and SsoOptions to the SamlAuthenticationOptions.

Would that meet your requirements?

The alternative is to not use the SamlAuthenticationHandler but instead call _samlServiceProvider.InitiateSsoAsync from your application. This allows you to specify relay state and SsoOptions.

[quote]
ComponentSpace - 6/16/2021
We could add the relay state and SsoOptions to the SamlAuthenticationOptions.

Would that meet your requirements?

The alternative is to not use the SamlAuthenticationHandler but instead call _samlServiceProvider.InitiateSsoAsync from your application. This allows you to specify relay state and SsoOptions.
[/quote]

Firstly, please don't take the below suggestions a criticism, we've got so much value out of the library to date that these are comparatively tiny issues.

Secondly, not using the handler also means having to re-implement all the things the handler does ( ssoSessionStore, SetConfigurationId, etc), the alternative is make the devs stop using the authN/AuthZ patterns they've become accustomed to in .NET Core. It also means I don't have keep abreast of all the changes made to the library... sort of defeats the purpose of using it otherwise.

Thirdly, the issue with adding these to the options is that these concerns may not be application scoped. Relay state and SSOOptions may be a function of the request, AuthProperties, and/or partner.

Microsoft's implementation of various Authentication handler events is a good example of allowing access to the working documents during the processing steps.
For example, with the OpenIdConnect handler, just before it's about to redirect to the identity provider, it raises a OnRedirectToIdentityProvider event with a lot of information. We can modify parts of the message before dispatch, or even take complete control.

In this specific example of the relay state, if you where to raise, say, an OnInitiateSSO event, suppying an InitiateSSOContext model, then see could modify anything relevant and allow the handler to use the updating information before dispatching to the provider. Possible properties on the context would be RelayState (read/write), SSOOptions (nullable/read/write), AuthenticationProperties (read only), PartnerName (readonly)

Given we can inject an ISamlServiceProviderEvents ( but unfortunately not in version 3.0.0 we're using ), it makes it possible to pull in other system dependencies so the Event system is really the best place for it.
At the moment I have to subclass the SamlAuthenticationHandler and reimplement SamlAuthenticationExtensions.AddSaml so I can inject an implementation of ISamlServiceProviderEvents

The same approach could be used in the SLO and Artifacts flows. I would like to add a bit more traceability for audit and logging purposes and really don't want to have to interpret the XML ( from the OnReceiveMessage event! ).

Essentially, look for all the places where the default handlers have omitted arguments and consider raising events. Subclassing all the real implementation just to tweak a few values on a methods here and there, especially when you don't have all the information at the time you need it ( i.e. AuthenticaionProperties ).

Thanks for your feedback. What you say makes sense.

In later releases, there’s a SamlAuthenticationEvents which extends the SamlServiceProviderEvents so you have access to various events if using the authentication handler. However, these don’t cover all the functionality you’d like to see.

We’ll look at making the SAML authentication handler more flexible, as per your suggestions, in upcoming releases.