AuthenticateResult.Succeeded is false with Okta and Sustainsys.SAML2

烈酒焚心 提交于 2021-02-02 03:41:39

问题


I have a .Net Core 2 application which leverages Sustainsys.Saml2.AspNetCor2 (2.7.0). The front end is an Angular application. The SAML approach I'm taking is based on, and very similar to, the approach taken in this reference implementation: https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample

*Everything works fine with the test IDP (https://stubidp.sustainsys.com).

But when we try to integrate with Okta, the AuthenticateResult.Succeeded property in the callback method (see below) is always false, even though the SAML posted to the ASC endpoint appears to indicate a successful authentication. We are not seeing any errors at all. It's just not succeeding.

(Note that my company does not have access to Okta - that is maintained by a partner company.)

Here is the server code in the controller:

[AllowAnonymous]
    [HttpPost, HttpGet]
    [Route("api/Security/InitiateSamlSingleSignOn")]
    public IActionResult InitiateSamlSingleSignOn(string returnUrl)
    {
      return new ChallengeResult(
          Saml2Defaults.Scheme,
          new AuthenticationProperties
          {
            RedirectUri = Url.Action(nameof(SamlLoginCallback), new { returnUrl })
          });
    }

    [AllowAnonymous]
    [HttpPost, HttpGet]
    [Route("api/Security/SamlLoginCallback")]
    public async Task<IActionResult> SamlLoginCallback(string returnUrl)
    {
      var authenticateResult = await HttpContext.AuthenticateAsync(ApplicationSamlConstants.External);

      if (!authenticateResult.Succeeded)
      {
        return Unauthorized();
      }
  
     // more code below, never reached
  
   }

Here is a screenshot of some of the SAML sent by Okta, captured using the Chrome extension, SAML-tracer:

I don't know how to investigate this further. Any help would be most appreciated!

In the ConfigureServices method, in case it's useful, I have the following (in relevant part):

public void ConfigureServices(IServiceCollection services)
{
  // [snip]
  if (usingSAML)
  {
    services.Configure<CookiePolicyOptions>(options =>
    {
      // SameSiteMode.None is required to support SAML SSO.
      options.MinimumSameSitePolicy = SameSiteMode.None;

      options.CheckConsentNeeded = context => false;

      // Some older browsers don't support SameSiteMode.None.
      options.OnAppendCookie = cookieContext => SameSite.CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
      options.OnDeleteCookie = cookieContext => SameSite.CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
    });
    
    authBuilder = services.AddAuthentication(o =>
    {
      o.DefaultScheme = ApplicationSamlConstants.Application;
      o.DefaultSignInScheme = ApplicationSamlConstants.External;
      o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
      o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    });

    authBuilder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
    {
      // see https://stackoverflow.com/questions/46243697/asp-net-core-persistent-authentication-custom-cookie-authentication
      options.ExpireTimeSpan = new System.TimeSpan(365, 0, 0, 0, 0);
      options.AccessDeniedPath = new PathString("/login");
      options.LoginPath = new PathString("/login");
    })
    .AddCookie(ApplicationSamlConstants.Application)
    .AddCookie(ApplicationSamlConstants.External)
    .AddSaml2(options =>
    {
      options.SPOptions.EntityId = new EntityId(this.Configuration["Saml:SPEntityId"]);
      options.IdentityProviders.Add(
          new IdentityProvider(
              new EntityId(this.Configuration["Saml:IDPEntityId"]), options.SPOptions)
          {
            MetadataLocation = this.Configuration["Saml:IDPMetaDataBaseUrl"],
            LoadMetadata = true,
          });
      options.SPOptions.ServiceCertificates.Add(new X509Certificate2(this.Configuration["Saml:CertificateFileName"]));
    });
  }
 // [snip]
}

UPDATE: I modified the code to capture more logging information, and what I have found is that, at the Saml2/Acs endpoint, the user is being authenticated. In the log files, I see this:

2020-09-14 09:28:09.307 -05:00 [DBG] Signature validation passed for Saml Response Microsoft.IdentityModel.Tokens.Saml2.Saml2Id
2020-09-14 09:28:09.369 -05:00 [DBG] Extracted SAML assertion id1622894416505593469999142
2020-09-14 09:28:09.385 -05:00 [INF] Successfully processed SAML response Microsoft.IdentityModel.Tokens.Saml2.Saml2Id and authenticated bankoetest@sfi.cloud

However, when I get to the SamlLoginCallback method, this authentication information is not present in the AuthenticateResult obtained by this call:

 var authenticateResult = await HttpContext.AuthenticateAsync(ApplicationSamlConstants.External);

My custom logging information for the authentication result object looks like this:

2020-09-14 09:28:09.432 -05:00 [ERR] SAML Authentication Failure: authenticateResult.Failure (Exception object) is null; 
No information was returned for the authentication scheme; 
authenticateResult.Principal is null; 
authenticateResult.Properties is null.
authenticateResult.Ticket is null.

What could be going wrong?


回答1:


The root cause here was ultimately the result of differences in the case of the Url used by Okta vs our code in redirect logic. The URLs matched, but the case did not. This caused cookies to be unreadable by later-invoked methods which were being sent to a URL which was different, even though the difference was only in the casing of the path. Once we made sure that all paths matched exactly, down to the casing, it started working.



来源:https://stackoverflow.com/questions/63853661/authenticateresult-succeeded-is-false-with-okta-and-sustainsys-saml2

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!