#StackBounty: #asp.net-core #saml-2.0 #okta #sustainsys-saml2 AuthenticateResult.Succeeded is false with Okta and Sustainsys.SAML2

Bounty: 50

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:
enter image description here

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?


Get this bounty!!!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.