| File: DeviceBoundSessionRegistrationHeader.cs | Web Access |
| Project: src\aspnetcore\src\Security\Authentication\DeviceBoundSessions\src\Microsoft.AspNetCore.Authentication.DeviceBoundSessions.csproj (Microsoft.AspNetCore.Authentication.DeviceBoundSessions) |
#pragma warning disable ASP0031 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Security.Claims; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Authentication.DeviceBoundSessions; /// <summary> /// Writes the DBSC <c>Secure-Session-Registration</c> response header, advertising the supported /// signing algorithms and a fresh registration challenge bound to a principal. Shared by the /// cookie-events wiring paths (the inline <c>OnSigningIn</c> delegate installed by /// <see cref="PostConfigureDeviceBoundSessionCookieOptions"/> and the /// <see cref="DeviceBoundSessionCookieEvents"/> wrapper) and by the public /// <see cref="DeviceBoundSessionHttpContextExtensions"/> on-demand entry point. /// </summary> internal static class DeviceBoundSessionRegistrationHeader { /// <summary> /// Emits the <c>Secure-Session-Registration</c> header onto the current response. /// </summary> /// <param name="httpContext">The current HTTP context.</param> /// <param name="principal">The principal the challenge is bound to. When <see langword="null"/> an empty principal is used.</param> /// <param name="dbscScheme">The resolved DBSC handler scheme name whose options drive the header.</param> public static void Emit(HttpContext httpContext, ClaimsPrincipal? principal, string dbscScheme) { // Dependencies are resolved from the request scope rather than constructor-injected: this is a // shared helper used by the inline OnSigningIn delegate (wired by the singleton // IPostConfigureOptions), the DI-less DeviceBoundSessionCookieEvents wrapper, and the public // HttpContext extension, none of which hold these dependencies. It runs per-request, so the // live HttpContext.RequestServices is the only source all callers share. (Both IOptionsMonitor<> // and the challenge protector are singletons, so request-scope resolution is for sharing, not // lifetime; the genuinely per-request input is the principal.) var dbscOptions = httpContext.RequestServices .GetRequiredService<IOptionsMonitor<DeviceBoundSessionOptions>>() .Get(dbscScheme); var challengeProtector = httpContext.RequestServices .GetRequiredService<DeviceBoundSessionChallengeProtector>(); var effectivePrincipal = principal ?? new ClaimsPrincipal(); var challenge = challengeProtector.GenerateRegistrationChallenge(effectivePrincipal, dbscOptions.ChallengeMaxAge); // Advertise the registration endpoint relative to the application's path base so an app mounted // under a non-root path base (e.g. "/foo") tells the browser to POST to "/foo/.well-known/dbsc/..." // rather than the origin root. var registrationPath = httpContext.Request.PathBase.Add(dbscOptions.RegistrationPath).ToUriComponent(); var headerValue = $"{DeviceBoundSessionConstants.AdvertisedAlgorithms};path={HeaderUtilities.EscapeAsQuotedString(registrationPath)};challenge={HeaderUtilities.EscapeAsQuotedString(challenge)}"; httpContext.Response.Headers.Append(DeviceBoundSessionConstants.Headers.Registration, headerValue); } }