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!
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();Additionally only for Net Framework 4.8, because of owin vs System.Web cookies bug, CookieManager should be used. Code: