Código
El código mostrado es solo una selección del código de ejemplo en GitHub.
El paquete ITfoxtec Identity Saml2 se integra en una aplicación ASP.NET Core MVC Relying Party (RP) mediante configuración en Startup y añadiendo un Auth Controller con los siguientes métodos. El binding mostrado puede cambiarse según los requisitos.
Además, es posible establecer algunos parámetros opcionales en Saml2AuthnRequest y Saml2LogoutRequest. En Saml2AuthnRequest, por ejemplo, se admite ForceAuthn, que obliga al usuario a ingresar credenciales incluso si ya existe un contexto SSO en el Security Token Service (STS) / Identity Provider (IdP).
Agregar configuración al método ConfigureServices en Startup
Configuración usando metadatos del IdP.
services.BindConfig<Saml2Configuration>(Configuration, "Saml2", (serviceProvider, saml2Configuration) =>
{
saml2Configuration.SigningCertificate = CertificateUtil.Load(AppEnvironment.MapToPhysicalFilePath(
Configuration["Saml2:SigningCertificateFile"]), Configuration["Saml2:SigningCertificatePassword"]);
saml2Configuration.AllowedAudienceUris.Add(saml2Configuration.Issuer);
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
var entityDescriptor = new EntityDescriptor();
entityDescriptor.ReadIdPSsoDescriptorFromUrlAsync(httpClientFactory, new Uri(Configuration["Saml2:IdPMetadata"])).GetAwaiter().GetResult();
if (entityDescriptor.IdPSsoDescriptor != null)
{
saml2Configuration.AllowedIssuer = entityDescriptor.EntityId;
saml2Configuration.SingleSignOnDestination = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.First().Location;
saml2Configuration.SingleLogoutDestination = entityDescriptor.IdPSsoDescriptor.SingleLogoutServices.First().Location;
saml2Configuration.SignatureValidationCertificates.AddRange(entityDescriptor.IdPSsoDescriptor.SigningCertificates);
}
else
{
throw new Exception("IdPSsoDescriptor not loaded from metadata.");
}
return saml2Configuration;
});
services.AddSaml2();
services.AddHttpClient();
Configuración sin metadatos.
services.BindConfig<Saml2Configuration>(Configuration, "Saml2", (serviceProvider, saml2Configuration) =>
{
saml2Configuration.SigningCertificate = CertificateUtil.Load(AppEnvironment.MapToPhysicalFilePath(
Configuration["Saml2:SigningCertificateFile"]), Configuration["Saml2:SigningCertificatePassword"]);
saml2Configuration.AllowedAudienceUris.Add(saml2Configuration.Issuer);
saml2Configuration.SignatureValidationCertificates.Add(CertificateUtil.Load(AppEnvironment.MapToPhysicalFilePath(
Configuration["Saml2:SignatureValidationCertificateFile"])));
return saml2Configuration;
});
services.AddSaml2();
Método de login en el Auth Controller
[Route("Login")]
public IActionResult Login(string returnUrl = null)
{
var binding = new Saml2RedirectBinding();
binding.SetRelayStateQuery(new Dictionary<string, string>
{ { relayStateReturnUrl, returnUrl ?? Url.Content("~/") } });
return binding.Bind(new Saml2AuthnRequest(config)).ToActionResult();
}
Método AssertionConsumerService en el Auth Controller
Después de un login exitoso o fallido, el método ACS recibe la respuesta.
[Route("AssertionConsumerService")]
public async Task<IActionResult> AssertionConsumerService()
{
var httpRequest = Request.ToGenericHttpRequest(validate: true);
var saml2AuthnResponse = new Saml2AuthnResponse(config);
httpRequest.Binding.ReadSamlResponse(httpRequest, saml2AuthnResponse);
if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
{
throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
}
httpRequest.Binding.Unbind(httpRequest, saml2AuthnResponse);
await saml2AuthnResponse.CreateSessionAsync(HttpContext,
claimsTransform: (claimsPrincipal) => Task.FromResult(ClaimsTransform.Transform(claimsPrincipal)));
var relayStateQuery = httpRequest.Binding.GetRelayStateQuery();
var returnUrl = relayStateQuery.ContainsKey(relayStateReturnUrl) ? relayStateQuery[relayStateReturnUrl] : Url.Content("~/");
return Redirect(returnUrl);
}
Método de logout en el Auth Controller
[HttpPost("Logout")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout()
{
if (!User.Identity.IsAuthenticated)
{
return Redirect(Url.Content("~/"));
}
var binding = new Saml2PostBinding();
var saml2LogoutRequest = await new Saml2LogoutRequest(config, User).DeleteSession(HttpContext);
return binding.Bind(saml2LogoutRequest).ToActionResult();
}
Método LoggedOut en el Auth Controller
Después de un logout exitoso o fallido, el método LoggedOut recibe la respuesta.
[Route("LoggedOut")]
public IActionResult LoggedOut()
{
var httpRequest = Request.ToGenericHttpRequest(validate: true);
httpRequest.Binding.Unbind(httpRequest, new Saml2LogoutResponse(config));
return Redirect(Url.Content("~/"));
}
Método SingleLogout en el Auth Controller
Recibe una solicitud Single Logout y envía una respuesta.
[Route("SingleLogout")]
public async Task<IActionResult> SingleLogout()
{
Saml2StatusCodes status;
var httpRequest = Request.ToGenericHttpRequest(validate: true);
var logoutRequest = new Saml2LogoutRequest(config, User);
try
{
httpRequest.Binding.Unbind(httpRequest, logoutRequest);
status = Saml2StatusCodes.Success;
await logoutRequest.DeleteSession(HttpContext);
}
catch (Exception exc)
{
// log exception
Debug.WriteLine("SingleLogout error: " + exc.ToString());
status = Saml2StatusCodes.RequestDenied;
}
var responseBinding = new Saml2PostBinding();
responseBinding.RelayState = httpRequest.Binding.RelayState;
var saml2LogoutResponse = new Saml2LogoutResponse(config)
{
InResponseToAsString = logoutRequest.IdAsString,
Status = status,
};
return responseBinding.Bind(saml2LogoutResponse).ToActionResult();
}