AuthenticateResult.Succeeded é falso com Okta e Sustainsys.SAML2
Eu tenho um aplicativo .Net Core 2 que aproveita o Sustainsys.Saml2.AspNetCor2 (2.7.0). O front end é um aplicativo Angular. A abordagem SAML que estou adotando é baseada e muito semelhante à abordagem adotada nesta implementação de referência:https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample
* Tudo funciona bem com o IDP de teste (https://stubidp.sustainsys.com)
Mas quando tentamos integrar com o Okta, a propriedade AuthenticateResult.Succeeded no método de retorno de chamada (veja abaixo) é sempre falsa, mesmo que o SAML postado no terminal ASC pareça indicar uma autenticação bem-sucedida. Não estamos vendo nenhum erro. Simplesmente não está dando certo.
(Observe que minha empresa não tem acesso ao Okta - que é mantido por uma empresa parceira.)
Aqui está o código do servidor no controlador:
[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
}
Aqui está uma captura de tela de alguns dos SAML enviados pela Okta, capturados usando a extensão do Chrome, SAML-tracer:
Não sei como investigar isso mais a fundo. Qualquer ajuda seria muito apreciada!
No método ConfigureServices, caso seja útil, tenho o seguinte (na parte relevante):
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]
}
ATUALIZAÇÃO: modifiquei o código para capturar mais informações de registro e o que descobri é que, no endpoint Saml2 / Acs, o usuário está sendo autenticado. Nos arquivos de log, vejo o seguinte:
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 [email protected]
No entanto, quando chego ao método SamlLoginCallback, essas informações de autenticação não estão presentes no AuthenticateResult obtido por esta chamada:
var authenticateResult = await HttpContext.AuthenticateAsync(ApplicationSamlConstants.External);
Minhas informações de registro personalizadas para o objeto de resultado de autenticação são assim:
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.
O que poderia dar errado?
Respostas
A causa raiz aqui foi, em última análise, o resultado das diferenças no caso do Url usado pelo Okta em relação ao nosso código na lógica de redirecionamento. Os URLs correspondem, mas o caso não. Isso tornava os cookies ilegíveis por métodos invocados posteriormente que estavam sendo enviados para uma URL diferente, embora a diferença estivesse apenas na caixa do caminho. Depois de nos certificarmos de que todos os caminhos correspondiam exatamente, até a caixa, ele começou a funcionar.