Closed TheArchitectIO closed 2 years ago
@TheArchitectIO Is there any update about this issue ?, I got same error trying to verify user is signed on as Saml2 scheme before to perform a Signout action.
var result = await HttpContext.AuthenticateAsync(Saml2Defaults.Scheme);
if (result?.Principal != null) {
await HttpContext.SignOutAsync(Saml2Defaults.Scheme);
}
nuget v2.8 netcore v2.2
after some research found this post from Anders Abel "The Saml2 handler cannot be used as an authencation scheme, it is a challenge scheme."
@IMrBean @TheArchitectIO Did either of you get past this error? I'm running into the same exception when calling HttpContext.AuthenticateAsync.
Well to bypass this nightmare I take as reference the work off @hmacat https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample/blob/master/ExampleWebApi/Startup.cs he built an example off a webapi using SAML2 authentication to set up authorization over api resources emitting a JWT after SAML2 login was successfully performed.
Bassically what I do was to put the cookie off the saml2scheme in an "External" scheme and leaving Saml2Scheme as the default challenge scheme.
`
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = IdentityConstants.ExternalScheme; //"Identity.External"
sharedOptions.DefaultChallengeScheme = Sustainsys.Saml2.AspNetCore2.Saml2Defaults.Scheme;
sharedOptions.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
services.AddAuthentication()
.AddCookie(ApplicationSamlConstants.External)
.AddSaml2(options =>
{
options.SPOptions.EntityId = new EntityId(authenticationConfigSection.GetValue<string>("Wtrealm"));
options.SPOptions.ReturnUrl = new Uri(authenticationConfigSection.GetValue<string>("audienceUri"));
options.SPOptions.AuthenticateRequestSigningBehavior = SigningBehavior.Never;
options.SPOptions.Saml2PSecurityTokenHandler = new Saml2PSecurityTokenHandler();
options.IdentityProviders.Add(
new IdentityProvider(
new EntityId(authenticationConfigSection.GetValue<string>("authorityName")), options.SPOptions)
{
MetadataLocation = authenticationConfigSection.GetValue<string>("MetadataAddress"),
AllowUnsolicitedAuthnResponse = true,
LoadMetadata = true,
Binding = Saml2BindingType.HttpRedirect
, SingleLogoutServiceBinding = Saml2BindingType.HttpRedirect
,DisableOutboundLogoutRequests=false
});
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.SPOptions.ServiceCertificates.Add(certificate);
options.SignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
So in that way aftter callback is called I can retrive the claims values off authenticathion stored in CookieAuthenticationDefaults.AuthenticationScheme in order to use them to authenticate on IdentityConstants.ExternalScheme; //"Identity.External"
var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
if (!authenticateResult.Succeeded)
{
return Unauthorized();
}
var claimsIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
var username = authenticateResult.Principal.Claims.FirstOrDefault(c=>c.Type.Equals(ClaimTypes.NameIdentifier)).Value.ToString();
var claims = new List
try
{
var principal = new ClaimsPrincipal(claimsIdentity);
HttpContext.User = new ClaimsPrincipal(claimsIdentity);
User.AddIdentity(claimsIdentity);
Thread.CurrentPrincipal= new ClaimsPrincipal(claimsIdentity);
await HttpContext.SignInAsync(
IdentityConstants.ExternalScheme,
principal, new AuthenticationProperties() { IsPersistent = true });
}
catch (Exception ex)
{
_logger.LogError(ex.StackTrace);
}
`
Now you are signed on your custon ExternalScheme and ApplicationCookiesScheme, so SamlScheme is a challenge scheme not an authenticathion one, there is the main reason because claims doesnt persist over Saml2Scheme.
to perform logout action, I call
`
await HttpContext.SignOutAsync(Saml2Defaults.Scheme, new AuthenticationProperties
{ RedirectUri = "/" });
var location = HttpContext.Response.Headers["Location"]; // SignOutAsync return the redirect logoutSaml2 url
and after you got the saml2logut request (location variable) you need to sign out the user from the AplicationCookiesScheme and from the "External" Scheme and after that just send the redirect action to the Idp with the saml2logout request
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
return Redirect(location);
`
@IMrBean Thanks for sharing your code, I really appreciate it! I wasn't able to get it to work... Regardless of which cookie I set in the Startup configuration (Identity Server's external cookie or the one you have above), only a cookie named "Saml2.{relayStateId}" is set and this happens when challenge is called, not after the IDP returns back to the challenge callback. Thus, calling HttpContext.SignInAsync returns a failed result, as there is no external cookie to act on.
After burning a week trying to make this work, I resorted to reading in the SamlResponse value that is POST'd as a base64 string, decoding that into XML, loading it into an XmlDocument, and then validating that document manually. This isn't nearly as sexy as being able to just call HttpContext.SignInAsync and having it magically validate the response and return the claims for me, however I don't know what else to do so...
You should not call Authenticate on the Saml2 scheme. Call it on the cookie scheme, which is the one preserving the session.
Attempting to Use AspNetCore2 plugin with cookies.
I do not need the overhead of Identity and that is why its not in use.
I use the [Authorize] Decorator on my Controller action and that is all.
Information needed
Additional info
Please include
Super Basic Startup