Skip to content

Device Bound Session Credentials (DBSC) for cookie authentication (prototype)#67388

Open
rokonec wants to merge 34 commits into
mainfrom
roman/dbsc-prototype
Open

Device Bound Session Credentials (DBSC) for cookie authentication (prototype)#67388
rokonec wants to merge 34 commits into
mainfrom
roman/dbsc-prototype

Conversation

@rokonec

@rokonec rokonec commented Jun 23, 2026

Copy link
Copy Markdown
Member

Device Bound Session Credentials (DBSC) for cookie authentication (prototype)

Draft / prototype. The public API is gated behind the experimental analyzer diagnostic ASP0030. The wire protocol is a W3C Editor's Draft ("may change at any moment") and is shipping single-vendor (Chromium) behind a flag, so both the protocol and this API are expected to change. This PR is for design review, not merge.

What this adds

A new experimental authentication component, Microsoft.AspNetCore.Authentication.DeviceBoundSessions, that implements the server side of Device Bound Session Credentials. DBSC binds a browser session to a device-held private key: the short-lived session cookie is periodically refreshed only when the browser can produce a fresh signed proof-of-possession, so a stolen cookie alone is useless.

It is designed as a drop-in hardening layer over an existing cookie auth scheme: you point it at a "source" sign-in scheme and it manages the registration handshake, a path-scoped refresh cookie, and a short-lived session cookie.

Shape of the API

builder.Services.AddAuthentication()
    .AddCookie("Application", o => { /* normal sign-in cookie */ })
    .AddDeviceBoundSession("Application", o =>
    {
        o.ShortLivedCookieExpiration = TimeSpan.FromMinutes(10);
        o.ScopeSpecifications.Add(new DeviceBoundSessionScopeRule { /* ... */ });
    });

All public types carry [Experimental("ASP0030")]; consumers must explicitly opt in.

How it works

  • Registration (/.well-known/dbsc/registration): validates the browser's signed proof JWT, binds it to the authenticated principal via a data-protected, time-limited challenge, then stamps a path-scoped refresh cookie (carrying the device public key + session id) and a short-lived session cookie.
  • Refresh (/.well-known/dbsc/refresh): authenticates the refresh cookie, issues a Secure-Session-Challenge (403), validates the returned proof JWT signature against the bound key and the challenge binding, then re-mints the short-lived session cookie.
  • Domain separation: registration and refresh challenges use distinct data-protection purposes, so a challenge from one flow can never be decrypted or confused as the other.
  • Lifetime: the refresh cookie inherits the source scheme's ExpireTimeSpan/SlidingExpiration and slides exactly like the auth cookie it replaces (no session-lifetime regression when DBSC is enabled). The session cookie stays deliberately short-lived and non-sliding.
  • Logging: per-reason LoggerMessage events for every validation failure (challenge undecryptable/malformed/mismatch, proof malformed/wrong-typ/unsupported-alg/bad-signature), at Debug, never logging secrets.

Components

  • Handler, options, extensions, and DTOs under src/Security/Authentication/DeviceBoundSessions/src/
  • Challenge protector (CBOR payloads, time-limited data protection) and JWT proof validator (ES256/RS256)
  • DbscDebugServer sample with a live dashboard that decodes cookies, proofs, and challenges for inspection
  • Tests for the challenge protector, JWT validator, and derived cookie protection/sliding

Testing

  • src/Security/Authentication/test/DeviceBoundSessions/ — 38 tests covering challenge round-trips and domain separation, JWT proof validation, data-protection of derived cookies, and refresh-cookie sliding inheritance.
  • Manually exercised end-to-end against Chromium (registration → refresh → slide) via the DbscDebugServer sample.

Open questions for review

  • Protocol is an unstable Editor's Draft; we have already absorbed breaking changes (header renames, 401→403 re-challenge). Is experimental gating the right call, or should this live out-of-band until the spec stabilizes?
  • Diagnostic id ASP0030 is reserved in docs/list-of-diagnostics.md; confirm allocation.
  • Cookie attributes advertised in the session instruction are currently hard-coded rather than derived from the session scheme's cookie builder.
  • DbscDebugServer is not sample but tool, we shall consider if we keep it, and if we add or replace it with some simple sample customers can follow
  • /plans/dbsc-design.md is residual planning document which shall be removed before merge, we keeping it for now for reviewers

References

Specification

Browser implementation & documentation

Chromium source code

TODO

javiercn and others added 20 commits June 3, 2026 17:48
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)

Includes:
- Registration/refresh middleware
- ES256/RS256 JWT validation
- Stateless challenge generation via DataProtection
- Sample app with HTTP logging for testing
- 16 unit/integration tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New separate authentication handler project following the OAuth/OIDC delegation
pattern. The DBSC handler manages the registration/refresh protocol and delegates
cookie stamping to separate cookie authentication schemes.

Architecture:
- Policy scheme routes auth: tries Session cookie first, falls back to source
- Registration handler reads source scheme, stamps Refresh + Session cookies
- Refresh handler reads Refresh cookie (path-scoped), stamps fresh Session cookie
- Source (long-lived) cookie deleted after registration

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sample app demonstrates the new DBSC architecture:
- Source cookie scheme with DeviceBoundSessionCookieEvents
- AddDeviceBoundSession() wires up refresh/session/policy schemes
- Policy scheme fallback verified working

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The v1 approach embedded DBSC directly in the cookie authentication handler.
This has been replaced by the v2 architecture in the separate
DeviceBoundSessions project which uses the OAuth/OIDC delegation pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tyModel JWT

- Challenges now use ITimeLimitedDataProtector (5min expiry) and include
  the user's NameIdentifier claim for binding validation
- Registration validates NameIdentifier from challenge matches the
  authenticated principal
- Cookie sign-in header emission is now automatic via IPostConfigureOptions
  (no manual DeviceBoundSessionCookieEvents wiring in sample)
- JWT validation uses Microsoft.IdentityModel.JsonWebTokens
- Base64Url encoding uses WebEncoders from framework
- Added DataProtection.Extensions and WebUtilities references

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove IsAspNetCoreApp and IsPackable=false (matches JwtBearer pattern)
- Revert SharedFramework.External.props changes
- Add Microsoft.IdentityModel.JsonWebTokens to Dependencies.props/Versions.props
  as a LatestPackageReference (same as OpenIdConnect, WsFederation)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace simple NameIdentifier with a stable user identifier that follows
the same priority as antiforgery: sub > NameIdentifier > UPN > SHA256
hash of all claims. This ensures the challenge is bound to the user
regardless of which identity claims are available.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Registration challenge: claimUid|nonce (no session ID, not known yet)
- Refresh challenge: claimUid|nonce|sessionId (binds to specific session)
- Use RandomNumberGenerator.Fill(Span<byte>) instead of GetBytes()
- Use stackalloc for nonce/hash buffers
- Remove the RegistrationSessionId constant (was a design smell)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace pipe-separated string with CBOR map encoding
  Registration: { uid: claimUid }
  Refresh: { uid: claimUid, sid: sessionId }
- Remove nonce (ITimeLimitedDataProtector already provides uniqueness
  and replay protection via timestamp + MAC)
- Use byte[] overload of ITimeLimitedDataProtector.Protect/Unprotect
- Add System.Formats.Cbor reference

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use CBOR definite-length array instead of map (simpler, smaller):
  Registration: [ claimUid ]
  Refresh: [ claimUid, sessionId ]
- Use WebEncoders.Base64UrlEncode/Decode consistently (not manual replace)
- ITimeLimitedDataProtector byte[] API is appropriate here (not hot path,
  ISpanDataProtector doesn't support time-limited)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Challenge payload is just consecutive CBOR text strings, no array framing:
  Registration: textstring(claimUid)
  Refresh: textstring(claimUid) textstring(sessionId)
- Default ShortLivedCookieExpiration changed from 30s to 10 minutes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Switch from Microsoft.IdentityModel.JsonWebTokens (new entry) to
  Microsoft.IdentityModel.Protocols.OpenIdConnect (already in repo,
  transitively includes JsonWebTokens)
- Remove added Dependencies.props and Versions.props entries
- ComputeClaimUid now uses CBOR encoding for claim triples:
  - Known claims (sub/nid/upn): CBOR sequence of 3 text strings
  - Fallback: SHA256 of CBOR-encoded sorted claims (no intermediate strings)
- No more string interpolation for claim UIDs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Split DeviceBoundSessionConfiguration.cs into separate files per class
- Split DeviceBoundSessionCookieEvents.cs into PostConfigure, EventsWrapper, SourceSchemes
- Move DeviceBoundSessionJwtResult to own file
- Remove unused SessionCookieSuffix and RefreshCookieSuffix from Defaults

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rotector

- PostConfigureDeviceBoundSessionDerivedCookieOptions copies HttpOnly,
  Secure, SameSite, Domain, ExpireTimeSpan from source cookie scheme
  to refresh and session cookie schemes
- Refresh cookie overrides path to /.well-known/dbsc
- DeviceBoundSessionChallengeProtector is now a non-static singleton
  that caches the ITimeLimitedDataProtector instance
- Handler creates protector from injected IDataProtectionProvider
- Removed TryAddSingleton for protector (handler creates its own)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reflects: NuGet package model, CBOR challenges without nonce,
ITimeLimitedDataProtector, antiforgery-style claim UID, cookie settings
inheritance via IPostConfigureOptions, 10min default expiry, file structure,
JWT validation via Microsoft.IdentityModel, singleton challenge protector.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pand debug sample

- emit Secure-Session-Challenge only on 403, not on success
- rename JSON DTOs to SessionInstruction/Scope/ScopeRule/Credential
- add DbscSampleV2 debug dashboard and HAR capture
- add challenge protector and JWT validator tests

🔒 - Generated by Copilot
…ntal

- add per-reason debug logging in challenge protector and JWT validator
- separate DP purposes for registration/refresh; fix CBOR validation
- gate public API as experimental (ASP0030); anchor refresh cookie lifetime
- rename DbscSampleV2 sample to DbscDebugServer; add gap tests

🔒 - Generated by Copilot
- inherit SlidingExpiration from the source scheme; drop refresh-scheme override
- stop anchoring refresh cookie lifetime at registration so it slides like the auth cookie
- add tests for refresh sliding inheritance and short-lived cookie staying fixed
- sample: surface the refresh cookie on the dashboard and decode challenges with split purposes

🔒 - Generated by Copilot
@rokonec

rokonec commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

Copilot AI and others added 2 commits June 23, 2026 16:08
Co-authored-by: rokonec <25249058+rokonec@users.noreply.github.com>
Co-authored-by: rokonec <25249058+rokonec@users.noreply.github.com>

Copilot AI commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Merge conflicts resolved. The only conflict was in AspNetCore.slnx — main had added aspnetcoretools.csproj to the /src/Tools/NativeAot/ folder, which I included alongside the existing test project. Merged in aad0d70.

@rokonec rokonec marked this pull request as ready for review June 23, 2026 16:13
@rokonec rokonec requested a review from tdykstra as a code owner June 23, 2026 16:13
Copilot AI review requested due to automatic review settings June 23, 2026 16:13
@rokonec rokonec requested a review from a team as a code owner June 23, 2026 16:13
rokonec added 4 commits July 9, 2026 10:43
CopyFromSource saved and restored target.Cookie.Name/Path around the
attribute copy, but the copy never touches Name or Path, so the
save-restore was a no-op. Remove it; callers set the refresh cookie path
explicitly and the session scheme keeps its configured name.

Addresses PR review feedback.

🤖 Generated by Copilot
Address PR review feedback on logout semantics and default-scheme
ownership:

- Sign-out of a DBSC source scheme now also clears the derived session
  and refresh cookies, so a plain SignOutAsync(sourceScheme) fully logs
  the user out. Applications never need to know the derived scheme names
  to clear them — just as they never need them to create them. Wired via
  a shared helper on DeviceBoundSessionCookieEvents, covering both the
  delegate-based and EventsType cookie-event configurations.
- Guard the registration cookie exchange: the handler signs out the
  source scheme to drop the long-lived cookie during registration, which
  is not a user logout, so it no longer clears the session/refresh
  cookies it just minted.
- Stop unconditionally overriding the application's default schemes.
  A new IPostConfigureOptions<AuthenticationOptions> upgrades the default
  authenticate scheme to the DBSC policy scheme only when it already
  resolves to a wrapped source scheme (otherwise the user would be logged
  out after registration deletes the source cookie). Sign-in/sign-out and
  unrelated defaults are left untouched.

Add unit tests for the conditional default-scheme upgrade and end-to-end
integration tests (sign-in, registration, sign-out) that verify the
registration exchange keeps the derived cookies and sign-out clears them.

🤖 Generated by Copilot
…struction

The JSON session instruction format (W3C DBSC §9.6) defines two root
fields that were missing from SessionInstruction:

- continue (boolean, default true) — lets registration/refresh terminate
  a session by returning false. Added and always emitted.
- allowed_refresh_initiators (list of strings) — limits which out-of-scope
  hosts may initiate refreshes. The DeviceBoundSessionOptions setting of the
  same name was never flowed into the JSON; BuildSessionInstruction now
  populates it (omitted when empty).

Add serialization and end-to-end tests confirming the option flows into the
registration response and that continue is emitted.

🤖 Generated by Copilot
An application mounted under a non-root path base (e.g. "/foo") advertised
and scoped its DBSC endpoints at the origin root, so registration and refresh
broke. Thread the request path base through every path DBSC emits:

- Registration header path and the session instruction refresh_url now include
  Request.PathBase.
- The refresh cookie is scoped with a RequestPathBaseCookieBuilder (AdditionalPath
  = the DBSC endpoints directory) so its path becomes "{path base}/.well-known/dbsc"
  per request, matching the advertised refresh endpoint.
- The credential attributes are derived from the built session cookie so the
  advertised Path (and Secure) exactly match the cookie we emit; otherwise the
  browser would treat the bound cookie as missing under a path base and loop.

Add an end-to-end test that registers under UsePathBase("/foo") and asserts the
registration header path, refresh_url, refresh/session cookie paths, and the
credential attributes path all carry the path base.

🤖 Generated by Copilot
options.Events.OnSigningIn = async context =>
{
DeviceBoundSessionRegistrationHeader.Emit(context.HttpContext, context.Principal, dbscScheme);
await priorSigningIn(context);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await priorSigningIn(context);
await priorSigningIn(context);
DeviceBoundSessionRegistrationHeader.Emit(context.HttpContext, context.Principal, dbscScheme);

I think we should let the user-registered OnSigningIn event modify the identity claim used for challenge binding before generating the Secure-Session-Registration header and add a regression test verifying this scenario works.

Comment on lines +43 to +44
DeviceBoundSessionRegistrationHeader.Emit(context.HttpContext, context.Principal, _dbscScheme);
await GetInnerEvents(context.HttpContext).SigningIn(context);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
DeviceBoundSessionRegistrationHeader.Emit(context.HttpContext, context.Principal, _dbscScheme);
await GetInnerEvents(context.HttpContext).SigningIn(context);
await GetInnerEvents(context.HttpContext).SigningIn(context);
DeviceBoundSessionRegistrationHeader.Emit(context.HttpContext, context.Principal, _dbscScheme);

Comment thread src/Security/Authentication/DeviceBoundSessions/plans/dbsc-design.md Outdated
/// MUST stay in lock-step with the algorithms the proof validator accepts (<see cref="Es256"/>,
/// <see cref="Rs256"/>).
/// </summary>
internal const string AdvertisedAlgorithms = "(" + Es256 + " " + Rs256 + ")";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's going to be easier to read with interpolated string.

Suggested change
internal const string AdvertisedAlgorithms = "(" + Es256 + " " + Rs256 + ")";
internal const string AdvertisedAlgorithms = $"({Es256} {Rs256})";

/// Gets or sets the session scope.
/// </summary>
[JsonPropertyName("scope")]
public SessionScope? Scope { get; set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a JSON session scope describing the resources covered by the session. This key MUST be present, except when the value of the continue key is false.

Can we have the proper nullability annotations to indicate that this property is never null when Continue is true?

/// Gets or sets the session credentials.
/// </summary>
[JsonPropertyName("credentials")]
public List<SessionCredential>? Credentials { get; set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a list of JSON session credentials describing the cookies protected by this session. This key MUST be present, except when the value of the continue key is false.

Same here about nullability annotations.

/// list). See W3C Device Bound Session Credentials §8.3.
/// </summary>
[JsonPropertyName("allowed_refresh_initiators")]
public List<string>? AllowedRefreshInitiators { get; set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a list of strings describing which out-of-scope hosts are allowed to initiate DBSC refreshes. Out-of-scope is defined based on the scope’s origin and include_site values. See § 8.3 Identify if a request is allowed to refresh for details. This key is OPTIONAL; if not present, the default value will be an empty list.

Per the spec, I think this should default to an empty list.

Comment on lines +32 to +33
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<SessionScopeRule>? ScopeSpecification { get; set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this default to empty list and not be nullable, similar to others I commented on?

https://w3c.github.io/webappsec-dbsc/#format-session-scope-instructions

a list of JSON session scope rules describing modifications to the default scope (the entire origin or site). This key is OPTIONAL; if not present, an empty list will be used.

Also I guess it shouldn't have JsonIgnore?

/// Gets or sets the cookie name.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; } = default!;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be marked as required and remove the default!?

/// Gets or sets the cookie attributes string.
/// </summary>
[JsonPropertyName("attributes")]
public string Attributes { get; set; } = default!;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a string containing the expected attributes of the protected cookie. See § 8.6 Identify if missing session credential for details on how this is used. This key is OPTIONAL; if not present, it will be the empty string.

Should this default to string.Empty and not default! (which is effectively null)

Comment on lines +69 to +79
if (Request.Path.Equals(Options.RegistrationPath))
{
await HandleRegistrationAsync();
return true;
}

if (Request.Path.Equals(Options.RefreshPath))
{
await HandleRefreshAsync();
return true;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That answers some of my previous questions, I missed this.

{
if (HttpMethods.IsPost(Request.Method))
{
if (Request.Path.Equals(Options.RegistrationPath))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An sf-parameter whose key is "path", and whose value is an sf-string, conveying the path to the registration endpoint. This may be relative to the current URL, or it may be a full URL. Entries without this parameter will be ignored in § 8.10 Process session registration.

Are we correctly considering that this might be relative or full url?

Are we supposed to do anything if the user-provided RegistrationPath is a full url that is completely in a different host?

{
if (HttpMethods.IsPost(Request.Method))
{
if (Request.Path.Equals(Options.RegistrationPath))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is PathString equality right? I think by default it's case insensitive, but maybe for the purpose of this feature we will want to have case sensitive comparison? I'm not really sure, but I thought it's worth bringing this up.

private async Task HandleRegistrationAsync()
{
// Extract the JWT proof from the Secure-Session-Response header
var proofHeader = Request.Headers[DeviceBoundSessionConstants.Headers.Proof].ToString();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is taking the full header as-is right? Should we try to move any parameters and ignore them?

https://w3c.github.io/webappsec-dbsc/#header-secure-session-response

This string MUST only contain the DBSC proof JWT. Any sf-parameters SHOULD be ignored.

Do we already have any common helpers for parsing headers properly?

return;
}

proofHeader = proofHeader.Trim('"');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to handle any escaping or decoding there?
Should we only trim exactly a single " at the end and exactly a single " at the start?

/// Gets or sets whether to include the entire site in the session scope.
/// Defaults to <c>false</c> (origin-only scope).
/// </summary>
public bool IncludeSite { get; set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not implementing this so far?

if (!authResult.Succeeded || authResult.Principal is null)
{
Logger.RegistrationNoSourceAuthentication();
Response.StatusCode = StatusCodes.Status401Unauthorized;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a place where we should throw AuthenticationFailureException instead?

Comment on lines +86 to +96
builder.AddCookie(refreshScheme, o =>
{
o.Cookie.Name = $".AspNetCore.{sourceScheme}.Dbsc.Refresh";
o.Cookie.Path = "/.well-known/dbsc";
});

// Add the session cookie scheme — settings copied from source, expiry overridden
builder.AddCookie(sessionScheme, o =>
{
o.Cookie.Name = $".AspNetCore.{sourceScheme}.Dbsc.Session";
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does refreshScheme has Path while sessionScheme doesn't?

Comment on lines +25 to +31
public string? RefreshScheme { get; set; }

/// <summary>
/// Gets or sets the authentication scheme used to stamp the short-lived session cookie.
/// Both registration and refresh call <c>SignInAsync</c> on this scheme.
/// </summary>
public string? SessionScheme { get; set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to differentiate the two schemes?

sessionIdHeader = sessionIdHeader.Trim('"');

// Authenticate against the refresh scheme (path-scoped refresh cookie)
var authResult = await Context.AuthenticateAsync(Options.RefreshScheme);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this is called while the short-lived cookies have expired, what is the authentication result that we get?

proofHeader = proofHeader.Trim('"');

// Validate the JWT proof against the public key from the refresh cookie
var jwtResult = await _jwtValidator.ValidateAsync(proofHeader, publicKeyJwk, expectedChallenge: null);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we supposed to ensure that the signed proof is corresponding to a recent challenge that we issued?
It seems like expectedChallenge: null will be skipping that?

Comment on lines +123 to +124
DeviceBoundSessionConstants.Es256 when string.Equals(jsonWebKey.Kty, "EC", StringComparison.Ordinal) && string.Equals(jsonWebKey.Crv, "P-256", StringComparison.Ordinal) => jsonWebKey,
DeviceBoundSessionConstants.Rs256 when string.Equals(jsonWebKey.Kty, "RSA", StringComparison.Ordinal) => jsonWebKey,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need those checks for Kty and Crv?

It existed only for review and design purposes. Not needed anymore.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants