You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Cookie-based sign-in today produces a bearer credential: whoever holds the cookie is the user. If the cookie is exfiltrated (infostealer malware, token theft), it can be replayed from any machine for the remaining lifetime of the session. Device Bound Session Credentials (DBSC) is a W3C spec that binds a browser session to a device-held private key: the browser proves possession of the key to obtain a short-lived cookie, so a stolen cookie stops working once it expires.
This proposal is the public surface for a new standalone, experimental package, Microsoft.AspNetCore.Authentication.DeviceBoundSessions, tracked by #66478 and implemented in #67388. It "[implements] the DBSC protocol (W3C spec) in the cookie authentication handler" using a "stateless two-cookie pattern": a long-lived cookie carrying the auth ticket plus the embedded public key (refresh-token equivalent) and a short-lived .Dbsc.Session cookie that is the device-bound credential (access-token equivalent), with registration and refresh endpoints handled by an IAuthenticationRequestHandler.
The design goal is that an app opts into DBSC by wrapping an existing cookie scheme — AddDeviceBoundSession(sourceScheme) — rather than rewriting its authentication setup. The primary target is ASP.NET Core Identity, whose application cookie (IdentityConstants.ApplicationScheme) is exactly the long-lived bearer credential DBSC is meant to protect. The handler derives the refresh/session/policy schemes, copies cookie settings from the source scheme, and routes authentication through a policy scheme, so app code and [Authorize] continue to work unchanged.
The API ships as a standalone NuGet package (not the shared framework) because it carries a third-party dependency (Microsoft.IdentityModel.JsonWebTokens) and System.Formats.Cbor, and the entire surface is gated behind the experimental diagnostic ASP0031 ("Experimental warning for Device Bound Sessions (DBSC) APIs") and versioned with ExperimentalVersionPrefix as a 0.x preview package.
Proposed API
Everything below is new API in a new assembly/package, Microsoft.AspNetCore.Authentication.DeviceBoundSessions. Nothing is added to or changed in an existing assembly, so there is no diff against a current surface. Every type is annotated [Experimental("ASP0031", UrlFormat = "https://aka.ms/aspnet/analyzer/{0}")].
New static classDeviceBoundSessionExtensions, the DI entry point:
New types in the feature namespace — the defaults class, the HttpContext extension used to opt already-signed-in users into DBSC, the protocol handler, its options, and the options-side scope rule:
The main scenario is an ASP.NET Core Identity app protecting its application cookie. DBSC wraps the existing Identity scheme; nothing else about the app's authentication setup changes:
#pragma warning disable ASP0031varbuilder=WebApplication.CreateBuilder(args);builder.Services.AddDbContext<ApplicationDbContext>(options =>options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));builder.Services.AddDefaultIdentity<IdentityUser>().AddEntityFrameworkStores<ApplicationDbContext>();// Bind the Identity application cookie to the device.builder.Services.AddAuthentication().AddDeviceBoundSession(IdentityConstants.ApplicationScheme);varapp=builder.Build();app.UseAuthentication();app.UseAuthorization();app.MapRazorPages();app.Run();
Existing app code is unaffected — [Authorize], SignInManager, and User.Identity keep working, because authentication is routed through the policy scheme that AddDeviceBoundSession registers:
Migrating users who are already signed in with an Identity cookie, without forcing a re-login. WriteDeviceBoundSessionRegistration emits the Secure-Session-Registration header for the current authenticated principal; DBSC-capable browsers start registration on the next response, and browsers that don't support DBSC ignore it:
// The DBSC bound session cookie follows .AspNetCore.{sourceScheme}.Dbsc.SessionconststringboundCookie=".AspNetCore.Identity.Application.Dbsc.Session";app.Use(async(context,next)=>{varalreadyBound=context.Request.Cookies.ContainsKey(boundCookie);varalreadyOffered=context.Request.Cookies.ContainsKey("dbsc-offered");if(context.User.Identity?.IsAuthenticated==true&&!alreadyBound&&!alreadyOffered){context.WriteDeviceBoundSessionRegistration(IdentityConstants.ApplicationScheme);context.Response.Cookies.Append("dbsc-offered","1",newCookieOptions{Path="/",Secure=true,HttpOnly=true,SameSite=SameSiteMode.Lax,});}awaitnext();});
Alternative Designs
Implementing DBSC inside the existing cookie handler vs. a separate scheme. An earlier iteration lived in Microsoft.AspNetCore.Authentication.Cookies; it was removed in favor of a dedicated handler project ("Remove v1 DBSC implementation from Cookies project", "Add DeviceBoundSessions handler project (v2 architecture)"). The current design instead composes existing building blocks: the DBSC handler owns only the protocol endpoints, while cookie handling is delegated to derived cookie schemes and a policy scheme selects between the bound session cookie and the source cookie.
Shared framework vs. standalone package. The feature was explicitly changed to "ship as NuGet package, not shared framework", because it takes a dependency on Microsoft.IdentityModel.JsonWebTokens for JWT proof validation and on System.Formats.Cbor.
Stateful vs. stateless session storage. The implementation uses a "stateless two-cookie pattern" with "stateless challenge generation via DataProtection", so no server-side session store is required. Server-side revocation via a shared ITicketStore is tracked separately in DBSC: share source scheme ITicketStore (server-side revocation) with derived cookies #67794.
Explicit scheme names vs. derived scheme names. Rather than making an Identity app declare four schemes, AddDeviceBoundSession derives {sourceScheme}.Dbsc.Refresh, {sourceScheme}.Dbsc.Session, and {sourceScheme}.Dbsc, and post-configures the default scheme so "the app never has to know (or wire) the derived DBSC scheme names". RegistrationSourceScheme / RefreshScheme / SessionScheme remain on the options as an escape hatch.
Advertised endpoint URLs vs. local handler paths.RegistrationPath / RefreshPath are deliberately restricted to nonempty, root-relative, application-local paths (no full URLs, network-path references, query strings, or fragments), even though "the DBSC protocol permits absolute and cross-origin same-site endpoint references". Splitting the advertised URL from the local handler path is tracked in DBSC: separate browser-advertised endpoint URLs from local handler paths #67850.
Risks
Experimental surface. Everything is annotated [Experimental("ASP0031")] and the package uses ExperimentalVersionPrefix (0.x), so consumers must suppress ASP0031 to use it and the shape is expected to change. ASP0031 is a new diagnostic ID reserved in docs/list-of-diagnostics.md.
New public API in a new package, so no breaking changes to existing assemblies; the ref-assembly delta is additive and lives entirely in the new assembly.
Scheme-name collisions. The derived scheme and cookie names are computed from the source scheme name ({sourceScheme}.Dbsc.*); an app that already registered a scheme with one of those names would conflict.
Security-sensitive defaults and deployment requirements. The registration header "is only honored over HTTPS", the callable API requires an authenticated principal, and correct operation behind a proxy requires trusted forwarded scheme/host/prefix handling before authentication and no permissive credentialed CORS on the refresh endpoint. Mis-deployment weakens the binding the feature exists to provide.
Caller-owned idempotency for WriteDeviceBoundSessionRegistration. "Emitting it repeatedly mints a new challenge each time, so the caller owns idempotency" — a naive call on every response would issue a fresh challenge per request.
IncludeSite = true has a topology constraint: it "requires the registration endpoint to be served from the registrable-domain host because the scope origin is derived from the registration request origin". Registration from subdomains is tracked in DBSC: support site scope registration from subdomains #67827.
Wire-format DTOs are public and mutable.SessionInstruction, SessionScope, SessionScopeRule, and SessionCredential mirror the W3C JSON shapes with get; set; properties and List<T> members; if the spec evolves, these types evolve with it.
Public unsealed handler/options types (DeviceBoundSessionHandler, DeviceBoundSessionOptions, DeviceBoundSessionScopeRule) are extensible by derivation, which constrains future changes once the experimental gate is removed.
DBSC implemented in cookie auth with a two-cookie pattern: "Implements the DBSC protocol (W3C spec) in the cookie authentication handler. Uses a stateless two-cookie pattern: Long-lived cookie: auth ticket + embedded public key (refresh token equivalent) / Short-lived cookie (__dbsc suffix): device-bound credential (access token equivalent) / Registration/refresh middleware / ES256/RS256 JWT validation / Stateless challenge generation via DataProtection" (commit eb04e4399c).
Wraps an existing cookie scheme: "Adds Device Bound Session Credentials (DBSC) authentication that wraps an existing cookie scheme. This sets up: a policy scheme (default auth), a refresh cookie scheme (path-scoped stash), a session cookie scheme (short-lived), and the DBSC protocol handler." (DeviceBoundSessionExtensions.cs).
Identity as the target scheme: "The existing cookie authentication scheme to protect with DBSC (e.g., "Identity.Application")" (DeviceBoundSessionExtensions.cs) and "The source cookie authentication scheme that was passed to AddDeviceBoundSession (for example IdentityConstants.ApplicationScheme)" (DeviceBoundSessionHttpContextExtensions.cs).
Policy-scheme routing: "Policy scheme routes auth: tries Session cookie first, falls back to source" (commit eb04e4399c).
Standalone experimental package: "Experimental (ASP0031); ships as a 0.x preview package, matching the convention for experimental standalone packages." and <Reference Include="Microsoft.IdentityModel.JsonWebTokens" />, <Reference Include="System.Formats.Cbor" /> (Microsoft.AspNetCore.Authentication.DeviceBoundSessions.csproj).
Package description: "ASP.NET Core middleware that enables Device Bound Session Credentials (DBSC) over an inner authentication scheme." (csproj).
Proposed API
Entire surface transcribed from src/Security/Authentication/DeviceBoundSessions/src/PublicAPI.Unshipped.txt, e.g. "static Microsoft.Extensions.DependencyInjection.DeviceBoundSessionExtensions.AddDeviceBoundSession(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder! builder, string! sourceScheme) -> Microsoft.AspNetCore.Authentication.AuthenticationBuilder!" and "const Microsoft.AspNetCore.Authentication.DeviceBoundSessions.DeviceBoundSessionDefaults.RegistrationPath = "/.well-known/dbsc/registration" -> string!".
New assembly (no existing-surface diff): the API entries live in a newly added PublicAPI.Unshipped.txt for a new project, "src/Security/Authentication/DeviceBoundSessions/src/Microsoft.AspNetCore.Authentication.DeviceBoundSessions.csproj" (commit eb04e4399c file list).
Experimental annotation on every type: "[Experimental("ASP0031", UrlFormat = "https://aka.ms/aspnet/analyzer/{0}")]" (all public source files).
Handler base/interface: "public class DeviceBoundSessionHandler : AuthenticationHandler, IAuthenticationRequestHandler" (DeviceBoundSessionHandler.cs).
W3C section mapping: "Corresponds to the "JSON Session Instruction Format" defined in W3C Device Bound Session Credentials §9.6", "§9.7", "§9.8", "§9.9" (SessionInstruction.cs, SessionScope.cs, SessionScopeRule.cs, SessionCredential.cs).
Credential shape: "public string Type { get; } = "cookie";", "public required string Name { get; set; }" (SessionCredential.cs).
Usage Examples
Registration/migration middleware sample is taken verbatim from the <example> block on WriteDeviceBoundSessionRegistration (DeviceBoundSessionHttpContextExtensions.cs), which itself uses IdentityConstants.ApplicationScheme and the .AspNetCore.Identity.Application.Dbsc.Session cookie name.
Wrapping the Identity cookie scheme follows the documented parameter contract "The existing cookie authentication scheme to protect with DBSC (e.g., "Identity.Application")" (DeviceBoundSessionExtensions.cs); the sample app demonstrates the same shape against its own source scheme: "services.AddAuthentication(DbscNames.Source) .AddCookie(DbscNames.Source, ...) ... .AddDeviceBoundSession(...)" (samples/DbscDebugServer/Program.cs).
Suppression pragma: "#pragma warning disable ASP0031 // Type is for evaluation purposes only and is subject to change or removal in future updates." (samples/DbscDebugServer/Program.cs).
Unchanged app code: "The app makes the source cookie its default scheme; AddDeviceBoundSession detects that and redirects the default authenticate scheme to its policy scheme, so the app never has to know (or wire) the derived DBSC scheme names." (samples/DbscDebugServer/Program.cs).
Alternative Designs
Handler split out of Cookies: "Remove v1 DBSC implementation from Cookies project" and "WIP: Add DeviceBoundSessions handler project (v2 architecture)" (commit history).
Packaging decision: "Fix DBSC project to ship as NuGet package, not shared framework" (commit history).
Derived scheme names: "var refreshScheme = $"{sourceScheme}.Dbsc.Refresh"; var sessionScheme = $"{sourceScheme}.Dbsc.Session"; var policyScheme = $"{sourceScheme}.Dbsc";" (DeviceBoundSessionExtensions.cs).
Path restrictions: "Although the DBSC protocol permits absolute and cross-origin same-site endpoint references, they are outside this component's local request-handler model." (DeviceBoundSessionOptions.cs); follow-up DBSC: separate browser-advertised endpoint URLs from local handler paths #67850 "DBSC: separate browser-advertised endpoint URLs from local handler paths".
Risks
HTTPS-only and idempotency: "Emitting it repeatedly mints a new challenge each time, so the caller owns idempotency" and "The header is only honored over HTTPS." (DeviceBoundSessionHttpContextExtensions.cs).
Authenticated-principal requirement: "The Device Bound Session registration challenge must be bound to an authenticated principal." (DeviceBoundSessionHttpContextExtensions.cs).
Proxy/CORS deployment guidance: "Production deployments behind a proxy must apply trusted forwarded scheme, host, and prefix before authentication... Do not enable permissive credentialed CORS on the refresh endpoint." (samples/DbscDebugServer/Program.cs).
IncludeSite constraint: "With the current implementation, true requires the registration endpoint to be served from the registrable-domain host because the scope origin is derived from the registration request origin." (DeviceBoundSessionOptions.cs); follow-up DBSC: support site scope registration from subdomains #67827 "DBSC: support site scope registration from subdomains".
Background and Motivation
Cookie-based sign-in today produces a bearer credential: whoever holds the cookie is the user. If the cookie is exfiltrated (infostealer malware, token theft), it can be replayed from any machine for the remaining lifetime of the session. Device Bound Session Credentials (DBSC) is a W3C spec that binds a browser session to a device-held private key: the browser proves possession of the key to obtain a short-lived cookie, so a stolen cookie stops working once it expires.
This proposal is the public surface for a new standalone, experimental package,
Microsoft.AspNetCore.Authentication.DeviceBoundSessions, tracked by #66478 and implemented in #67388. It "[implements] the DBSC protocol (W3C spec) in the cookie authentication handler" using a "stateless two-cookie pattern": a long-lived cookie carrying the auth ticket plus the embedded public key (refresh-token equivalent) and a short-lived.Dbsc.Sessioncookie that is the device-bound credential (access-token equivalent), with registration and refresh endpoints handled by anIAuthenticationRequestHandler.The design goal is that an app opts into DBSC by wrapping an existing cookie scheme —
AddDeviceBoundSession(sourceScheme)— rather than rewriting its authentication setup. The primary target is ASP.NET Core Identity, whose application cookie (IdentityConstants.ApplicationScheme) is exactly the long-lived bearer credential DBSC is meant to protect. The handler derives the refresh/session/policy schemes, copies cookie settings from the source scheme, and routes authentication through a policy scheme, so app code and[Authorize]continue to work unchanged.The API ships as a standalone NuGet package (not the shared framework) because it carries a third-party dependency (
Microsoft.IdentityModel.JsonWebTokens) andSystem.Formats.Cbor, and the entire surface is gated behind the experimental diagnosticASP0031("Experimental warning for Device Bound Sessions (DBSC) APIs") and versioned withExperimentalVersionPrefixas a 0.x preview package.Proposed API
Everything below is new API in a new assembly/package,
Microsoft.AspNetCore.Authentication.DeviceBoundSessions. Nothing is added to or changed in an existing assembly, so there is no diff against a current surface. Every type is annotated[Experimental("ASP0031", UrlFormat = "https://aka.ms/aspnet/analyzer/{0}")].New static class
DeviceBoundSessionExtensions, the DI entry point:New types in the feature namespace — the defaults class, the
HttpContextextension used to opt already-signed-in users into DBSC, the protocol handler, its options, and the options-side scope rule:New types modelling the W3C wire formats that are serialized to the browser during registration and refresh:
Implementation reference: PR #67388 (commit
eb04e4399c), in particularsrc/Security/Authentication/DeviceBoundSessions/src/PublicAPI.Unshipped.txt.Usage Examples
The main scenario is an ASP.NET Core Identity app protecting its application cookie. DBSC wraps the existing Identity scheme; nothing else about the app's authentication setup changes:
Existing app code is unaffected —
[Authorize],SignInManager, andUser.Identitykeep working, because authentication is routed through the policy scheme thatAddDeviceBoundSessionregisters:Tuning the bound-cookie lifetime, challenge freshness, and session scope:
Migrating users who are already signed in with an Identity cookie, without forcing a re-login.
WriteDeviceBoundSessionRegistrationemits theSecure-Session-Registrationheader for the current authenticated principal; DBSC-capable browsers start registration on the next response, and browsers that don't support DBSC ignore it:Alternative Designs
Implementing DBSC inside the existing cookie handler vs. a separate scheme. An earlier iteration lived in
Microsoft.AspNetCore.Authentication.Cookies; it was removed in favor of a dedicated handler project ("Remove v1 DBSC implementation from Cookies project", "Add DeviceBoundSessions handler project (v2 architecture)"). The current design instead composes existing building blocks: the DBSC handler owns only the protocol endpoints, while cookie handling is delegated to derived cookie schemes and a policy scheme selects between the bound session cookie and the source cookie.Shared framework vs. standalone package. The feature was explicitly changed to "ship as NuGet package, not shared framework", because it takes a dependency on
Microsoft.IdentityModel.JsonWebTokensfor JWT proof validation and onSystem.Formats.Cbor.Stateful vs. stateless session storage. The implementation uses a "stateless two-cookie pattern" with "stateless challenge generation via DataProtection", so no server-side session store is required. Server-side revocation via a shared
ITicketStoreis tracked separately in DBSC: share source scheme ITicketStore (server-side revocation) with derived cookies #67794.Explicit scheme names vs. derived scheme names. Rather than making an Identity app declare four schemes,
AddDeviceBoundSessionderives{sourceScheme}.Dbsc.Refresh,{sourceScheme}.Dbsc.Session, and{sourceScheme}.Dbsc, and post-configures the default scheme so "the app never has to know (or wire) the derived DBSC scheme names".RegistrationSourceScheme/RefreshScheme/SessionSchemeremain on the options as an escape hatch.Advertised endpoint URLs vs. local handler paths.
RegistrationPath/RefreshPathare deliberately restricted to nonempty, root-relative, application-local paths (no full URLs, network-path references, query strings, or fragments), even though "the DBSC protocol permits absolute and cross-origin same-site endpoint references". Splitting the advertised URL from the local handler path is tracked in DBSC: separate browser-advertised endpoint URLs from local handler paths #67850.Risks
Experimental surface. Everything is annotated
[Experimental("ASP0031")]and the package usesExperimentalVersionPrefix(0.x), so consumers must suppressASP0031to use it and the shape is expected to change.ASP0031is a new diagnostic ID reserved indocs/list-of-diagnostics.md.New public API in a new package, so no breaking changes to existing assemblies; the ref-assembly delta is additive and lives entirely in the new assembly.
Scheme-name collisions. The derived scheme and cookie names are computed from the source scheme name (
{sourceScheme}.Dbsc.*); an app that already registered a scheme with one of those names would conflict.Security-sensitive defaults and deployment requirements. The registration header "is only honored over HTTPS", the callable API requires an authenticated principal, and correct operation behind a proxy requires trusted forwarded scheme/host/prefix handling before authentication and no permissive credentialed CORS on the refresh endpoint. Mis-deployment weakens the binding the feature exists to provide.
Caller-owned idempotency for
WriteDeviceBoundSessionRegistration. "Emitting it repeatedly mints a new challenge each time, so the caller owns idempotency" — a naive call on every response would issue a fresh challenge per request.IncludeSite = truehas a topology constraint: it "requires the registration endpoint to be served from the registrable-domain host because the scope origin is derived from the registration request origin". Registration from subdomains is tracked in DBSC: support site scope registration from subdomains #67827.Wire-format DTOs are public and mutable.
SessionInstruction,SessionScope,SessionScopeRule, andSessionCredentialmirror the W3C JSON shapes withget; set;properties andList<T>members; if the spec evolves, these types evolve with it.Public unsealed handler/options types (
DeviceBoundSessionHandler,DeviceBoundSessionOptions,DeviceBoundSessionScopeRule) are extensible by derivation, which constrains future changes once the experimental gate is removed.Source justification (per
.github/prompts/ApiReview.prompt.md)Background and Motivation
eb04e4399c).DeviceBoundSessionExtensions.cs).DeviceBoundSessionExtensions.cs) and "The source cookie authentication scheme that was passed to AddDeviceBoundSession (for example IdentityConstants.ApplicationScheme)" (DeviceBoundSessionHttpContextExtensions.cs).eb04e4399c).<Reference Include="Microsoft.IdentityModel.JsonWebTokens" />,<Reference Include="System.Formats.Cbor" />(Microsoft.AspNetCore.Authentication.DeviceBoundSessions.csproj).__ASP0031__| Experimental warning for Device Bound Sessions (DBSC) APIs" (docs/list-of-diagnostics.md).Proposed API
src/Security/Authentication/DeviceBoundSessions/src/PublicAPI.Unshipped.txt, e.g. "static Microsoft.Extensions.DependencyInjection.DeviceBoundSessionExtensions.AddDeviceBoundSession(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder! builder, string! sourceScheme) -> Microsoft.AspNetCore.Authentication.AuthenticationBuilder!" and "const Microsoft.AspNetCore.Authentication.DeviceBoundSessions.DeviceBoundSessionDefaults.RegistrationPath = "/.well-known/dbsc/registration" -> string!".PublicAPI.Unshipped.txtfor a new project, "src/Security/Authentication/DeviceBoundSessions/src/Microsoft.AspNetCore.Authentication.DeviceBoundSessions.csproj" (commiteb04e4399cfile list).DeviceBoundSessionHandler.cs).DeviceBoundSessionOptions.cs).SessionInstruction.cs,SessionScope.cs,SessionScopeRule.cs,SessionCredential.cs).SessionCredential.cs).Usage Examples
<example>block onWriteDeviceBoundSessionRegistration(DeviceBoundSessionHttpContextExtensions.cs), which itself usesIdentityConstants.ApplicationSchemeand the.AspNetCore.Identity.Application.Dbsc.Sessioncookie name.DeviceBoundSessionExtensions.cs); the sample app demonstrates the same shape against its own source scheme: "services.AddAuthentication(DbscNames.Source) .AddCookie(DbscNames.Source, ...) ... .AddDeviceBoundSession(...)" (samples/DbscDebugServer/Program.cs).samples/DbscDebugServer/Program.cs).samples/DbscDebugServer/Program.cs).Alternative Designs
eb04e4399c); server-side revocation deferred per DBSC: share source scheme ITicketStore (server-side revocation) with derived cookies #67794 "DBSC: share source scheme ITicketStore (server-side revocation) with derived cookies".DeviceBoundSessionExtensions.cs).DeviceBoundSessionOptions.cs); follow-up DBSC: separate browser-advertised endpoint URLs from local handler paths #67850 "DBSC: separate browser-advertised endpoint URLs from local handler paths".Risks
DeviceBoundSessionHttpContextExtensions.cs).DeviceBoundSessionHttpContextExtensions.cs).samples/DbscDebugServer/Program.cs).IncludeSiteconstraint: "With the current implementation, true requires the registration endpoint to be served from the registrable-domain host because the scope origin is derived from the registration request origin." (DeviceBoundSessionOptions.cs); follow-up DBSC: support site scope registration from subdomains #67827 "DBSC: support site scope registration from subdomains"./cc #66478 #67388