diff --git a/samples/Contruum/Contruum.Server/Handlers.cs b/samples/Contruum/Contruum.Server/Handlers.cs index 9122e1ec..ba95ce9d 100644 --- a/samples/Contruum/Contruum.Server/Handlers.cs +++ b/samples/Contruum/Contruum.Server/Handlers.cs @@ -1,6 +1,12 @@ -using System.Globalization; +using System; +using System.Globalization; +using System.Security.Cryptography; +using System.Text; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Configuration; using OpenIddict.Abstractions; using OpenIddict.Server; using static OpenIddict.Abstractions.OpenIddictConstants; @@ -55,4 +61,46 @@ public ValueTask HandleAsync(HandleUserinfoRequestContext context) return default; } } + + public class AttachCheckSessionIframeEndpoint : IOpenIddictServerHandler + { + private readonly IConfiguration _configuration; + + public AttachCheckSessionIframeEndpoint(IConfiguration configuration) + { + _configuration = configuration; + } + + public ValueTask HandleAsync(HandleConfigurationRequestContext context) + { + var baseUri = context.BaseUri ?? throw new InvalidOperationException("Missing BaseUri"); + var relativePath = _configuration["OpenIddict:Endpoints:CheckSession"]!; + + context.Metadata["check_session_iframe"] = baseUri.ToString().TrimEnd('/') + "/" + relativePath.TrimStart('/'); + + return default; + } + } + + public class AttachSessionState : IOpenIddictServerHandler + { + public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) + { + if (context.Request?.ClientId is string clientId + && Uri.TryCreate(context.Request.RedirectUri, UriKind.Absolute, out var redirectUri) + && context.Transaction.GetHttpRequest()?.GetSessionId() is string sessionId) + { + var origin = redirectUri.GetLeftPart(UriPartial.Authority); + var salt = RandomNumberGenerator.GetHexString(8); + + var utf8Bytes = Encoding.UTF8.GetBytes(clientId + origin + sessionId + salt); + var hashBytes = SHA256.HashData(utf8Bytes); + var hashBase64Url = Base64UrlTextEncoder.Encode(hashBytes); + + context.Response.SetParameter("session_state", hashBase64Url + "." + salt); + } + + return default; + } + } } diff --git a/samples/Contruum/Contruum.Server/Pages/Connect/CheckSession.cshtml b/samples/Contruum/Contruum.Server/Pages/Connect/CheckSession.cshtml new file mode 100644 index 00000000..42128702 --- /dev/null +++ b/samples/Contruum/Contruum.Server/Pages/Connect/CheckSession.cshtml @@ -0,0 +1,49 @@ +@page +@{ + Layout = null; +} + + +OIDC Check Session + diff --git a/samples/Contruum/Contruum.Server/SessionIdExtensions.cs b/samples/Contruum/Contruum.Server/SessionIdExtensions.cs new file mode 100644 index 00000000..10b27bd8 --- /dev/null +++ b/samples/Contruum/Contruum.Server/SessionIdExtensions.cs @@ -0,0 +1,33 @@ +using System.Security.Cryptography; +using Microsoft.AspNetCore.Http; + +namespace Contruum.Server; + +public static class SessionIdExtensions +{ + public const string CookieName = "oidc_session"; + + public static void IssueSessionCookie(this HttpResponse response) + { + var sessionId = RandomNumberGenerator.GetHexString(32); + response.Cookies.Append( + CookieName, + sessionId, + new() + { + SameSite = SameSiteMode.None, + Secure = true, + HttpOnly = false, + }); + } + + public static void DeleteSessionCookie(this HttpResponse response) + { + response.Cookies.Delete(CookieName); + } + + public static string? GetSessionId(this HttpRequest request) + { + return request.Cookies[CookieName]; + } +} diff --git a/samples/Contruum/Contruum.Server/Startup.cs b/samples/Contruum/Contruum.Server/Startup.cs index 5a23830c..23b99949 100644 --- a/samples/Contruum/Contruum.Server/Startup.cs +++ b/samples/Contruum/Contruum.Server/Startup.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading.Tasks; using Contruum.Server.Models; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; @@ -37,6 +38,16 @@ public void ConfigureServices(IServiceCollection services) options.AccessDeniedPath = "/connect/signin"; options.LoginPath = "/connect/signin"; options.LogoutPath = "/connect/signout"; + options.Events.OnSignedIn = (context) => + { + context.Response.IssueSessionCookie(); + return Task.CompletedTask; + }; + options.Events.OnSigningOut = (context) => + { + context.Response.DeleteSessionCookie(); + return Task.CompletedTask; + }; }); // OpenIddict offers native integration with Quartz.NET to perform scheduled tasks @@ -101,6 +112,12 @@ public void ConfigureServices(IServiceCollection services) // Register the event handler responsible for populating userinfo responses. options.AddEventHandler(options => options.UseSingletonHandler()); + + options.AddEventHandler(options => + options.UseSingletonHandler()); + + options.AddEventHandler(options => + options.UseSingletonHandler()); }) .AddValidation(options => diff --git a/samples/Contruum/Contruum.Server/appsettings.json b/samples/Contruum/Contruum.Server/appsettings.json index 4c3a6e4d..db303927 100644 --- a/samples/Contruum/Contruum.Server/appsettings.json +++ b/samples/Contruum/Contruum.Server/appsettings.json @@ -95,7 +95,8 @@ "Introspection": "connect/introspect", "Token": "connect/token", "Userinfo": "connect/userinfo", - "Logout": "connect/endsession" + "Logout": "connect/endsession", + "CheckSession": "connect/checksession" }, "Scopes": [