diff --git a/src/Authentication.Saml2/source/Extensions.cs b/src/Authentication.Saml2/source/Extensions.cs index e508220..98dbd6a 100644 --- a/src/Authentication.Saml2/source/Extensions.cs +++ b/src/Authentication.Saml2/source/Extensions.cs @@ -8,7 +8,7 @@ namespace Passingwind.AspNetCore.Authentication.Saml2; internal static class Extensions { - public static ITfoxtec.Identity.Saml2.Http.HttpRequest ToGenericHttpRequest(this HttpRequest request, bool readBodyAsString = false) + public static ITfoxtec.Identity.Saml2.Http.HttpRequest ToGenericHttpRequest(this HttpRequest request) { return new ITfoxtec.Identity.Saml2.Http.HttpRequest { diff --git a/src/Authentication.Saml2/source/Saml2Handler.cs b/src/Authentication.Saml2/source/Saml2Handler.cs index fb73fd2..8e283ed 100644 --- a/src/Authentication.Saml2/source/Saml2Handler.cs +++ b/src/Authentication.Saml2/source/Saml2Handler.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Security.Authentication; using System.Security.Claims; using System.Text.Encodings.Web; @@ -29,6 +30,18 @@ public Saml2Handler(IOptionsMonitor options, ILoggerFactory logger { } + public override async Task HandleRequestAsync() + { + if (Options.RemoteSignOutPath.HasValue && Options.RemoteSignOutPath == Request.Path) + { + await HandleSignOutAsync(new AuthenticationProperties()); + + return true; + } + + return await base.HandleRequestAsync(); + } + protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { properties ??= new AuthenticationProperties(); @@ -83,7 +96,48 @@ protected override async Task HandleChallengeAsync(AuthenticationProperties prop public Task SignOutAsync(AuthenticationProperties? properties) { - return Task.CompletedTask; + var target = ResolveTarget(Options.ForwardSignOut); + return (target != null) + ? Context.SignOutAsync(target, properties) + : HandleSignOutAsync(properties ?? new AuthenticationProperties()); + } + + protected virtual async Task HandleSignOutAsync(AuthenticationProperties? properties) + { + _configuration ??= await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); + + Saml2StatusCodes status; + var requestBinding = new Saml2PostBinding(); + var logoutRequest = new Saml2LogoutRequest(_configuration, Context.User); + + try + { + requestBinding.Unbind(Request.ToGenericHttpRequest(), logoutRequest); + status = Saml2StatusCodes.Success; + + await Context.SignOutAsync(Options.SignOutScheme); + } + catch (Exception exc) + { + Logger.LogError(exc, "Saml2 single logout error"); + status = Saml2StatusCodes.RequestDenied; + } + + var responseBinding = new Saml2PostBinding + { + RelayState = requestBinding.RelayState + }; + + var saml2LogoutResponse = new Saml2LogoutResponse(_configuration) + { + InResponseToAsString = logoutRequest.IdAsString, + Status = status, + }; + responseBinding = responseBinding.Bind(saml2LogoutResponse); + + Response.Headers.CacheControl = "no-cache, no-store"; + Response.ContentType = "text/html"; + await Response.WriteAsync(responseBinding.PostContent); } protected override async Task HandleRemoteAuthenticateAsync()