'Sustainsys.Saml2 - Saml2/Acs endpoint returns Error 500 when processing SSO
I've created a C# ASP .Net Core 6.0 application, and trying to implement SSO with Azure AD using Sustainsys.Saml2, specifically with the Sustainsys.Saml2.AspNetCore2 package. Having tested the implementation on my development machine with localhost, I can see it works as expected and authenticates the user, populates the Identity model, and redirects to correct URL.
However, when deployed into the test environment, using a dockerized version, the behaviour changes. When triggering SSO, the user is authenticated successfully in Azure, but when returning to the app, it returns an Error 500 at the Saml2/Acs endpoint. Reviewing the logs show no indication of any errors, and instead report successful authentication for the user.
The Program.cs configuration:
builder.Services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = "Saml2";
})
.AddSaml2(options =>
{
var logger = new LoggerFactory();
options.SPOptions.Logger = new AspNetCoreLoggerAdapter(logger.CreateLogger<Saml2Handler>());
options.SPOptions.EntityId = new EntityId(AppConfig.Saml_EntityID);
options.IdentityProviders.Add(
new IdentityProvider(new EntityId(AppConfig.Saml_AzureID), options.SPOptions)
{
Binding = Saml2BindingType.HttpRedirect,
LoadMetadata = true,
MetadataLocation = AppConfig.Saml_Metadata,
DisableOutboundLogoutRequests = false,
AllowUnsolicitedAuthnResponse = true
});
options.SPOptions.PublicOrigin = new Uri(AppConfig.BaseUrl);
options.SPOptions.ReturnUrl = new Uri(AppConfig.BaseUrl);
options.SPOptions.WantAssertionsSigned = true;
options.SPOptions.AuthenticateRequestSigningBehavior = SigningBehavior.Always;
options.SPOptions.ServiceCertificates.Add(new X509Certificate2(AppConfig.Saml_Cert_Path));
})
.AddCookie();
While troubleshooting the issue, I stumbled across some confusing behaviour that may or may not indicate what may be the issue. If I follow the following steps, I can end up at a point where the user is authenticated and can use the applications:
- Click 'Login' to trigger the Saml authentication.
- Hit the Error 500 at Saml2/Acs.
- Click 'refresh', and 'continue' to resubmit the request.
- The browser then continues to the intended URL, but says 'Connection Refused'
- Use the browser back buttons to return to the application home screen, and refresh the page... Viola! Logged in!
Furthermore, when inspecting the request headers on the Saml2/Acs endpoint, I can see a Saml response is returned, which I can manually decode from base64 and read the correct information!
As mentioned, the logs don't mention any errors, just:
Initiating login to https://sts.windows.net/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/
and
Successfully processed SAML response _ba082bb8-7d2c-4aa4-a7dc-b1520312d084 and authenticated a*******@********.com
Any assistance, or guidance to a resolution would be much appreciated!
Solution 1:[1]
Maybe this is not relevant for .Net Core, but for .net framework 4.8 there was the following issues:
ReturnUrl of Service Provider was wrong:
http://locahost/mysite/saml2/acsinstead of correct onehttp://locahost/mysite/(with trailing slash). Because of this, there was indefinite loop to http://locahost/mysite/saml2/acs`.SPOptions spOptions = new SPOptions() { EntityId = new EntityId(spMetadataUrl), ReturnUrl = new Uri(hostUrl + "/"), DiscoveryServiceUrl = new Uri(hostUrl + @"/DiscoveryService"), Organization = organization, AuthenticateRequestSigningBehavior = SigningBehavior.Never, RequestedAuthnContext = requestedAuthnContext, Logger = logger, PublicOrigin = hostUri };DO NOT USE
UseExternalSignInCookiemehod, otherwise ClaimPrincipal will not set for current Thread (cookies will be parsed to claims, although latter will not be set, this can be checked with code below):
Saml2AuthenticationOptions options = CreateSaml2Options(configuration, certificate); options.SPOptions.Saml2PSecurityTokenHandler = new MySaml2PSecurityTokenHandler();
public class MySaml2PSecurityTokenHandler : Sustainsys.Saml2.Saml2P.Saml2PSecurityTokenHandler
{
protected override ClaimsIdentity CreateClaimsIdentity(Saml2SecurityToken samlToken, string issuer, TokenValidationParameters validationParameters)
{
ClaimsIdentity identity = base.CreateClaimsIdentity(samlToken, issuer, validationParameters);
Claim claim = new Claim("Name", "jon.doe");
Claim[] claims = new Claim[] { claim };
identity.AddClaims(claims);
return identity;
}
}
Additionally only for Net Framework 4.8, because of owin vs System.Web cookies bug, CookieManager should be used. Code:
var cookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebCookieManager();
Saml2AuthenticationOptions options = CreateSaml2Options(configuration, certificate);
CookieAuthenticationOptions cookieAuthentication = new CookieAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString(configuration.ServiceProviderSignOnUrl),
CookieManager = ?ookieManager,
ReturnUrlParameter = GetBase() + "/",
Provider = new CookieAuthenticationProvider()
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = (context) =>
{
var newIdentity = new ClaimsIdentity(context.Identity);
int newcount = 1;
newIdentity.AddClaim(new Claim("SIMPLECOUNT", newcount.ToString()));
context.ReplaceIdentity(newIdentity);
return Task.FromResult<object>(null);
}
}
};
app.UseCookieAuthentication(cookieAuthentication);
app.SetDefaultSignInAsAuthenticationType(cookieAuthentication.AuthenticationType);
//app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseSaml2Authentication(options);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
