Skip to content

Synchronize JWT values into claims and cookie

Field Value
Operation ID OP-05
Trigger JwtClaimMiddleware.InvokeAsync, every request
Inputs HttpContext, current cookie principal
Outputs Updated claims; re-issued cookie, or an exception
Primary components JwtClaimMiddleware, JwtAuthorizationCookieClaimService.UpdateJwtTokenValues, ClaimsPrincipalExtensions.AddUpdateClaim
Entry point src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Middleware/JwtClaimMiddleware.cs L38–51; src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/JwtAuthorizationCookieClaimService.cs L54–77

Steps

  1. If context.User.Identity.IsAuthenticated is false, go to step 6 (BR-OIDC-11).
  2. info = UpdateJwtTokenValues(context), which calls AuthenticateAsync("Cookies").
  3. On failure: if info.Failure != null, log Error and return info.
  4. Inside the service: id = idToken ?? GetTokenValue("id_token"), access = accessToken ?? GetTokenValue("access_token"). principal.AddUpdateClaim("id_token", id) and AddUpdateClaim("access_token", access); AddUpdateClaim removes the first matching claim and adds the new one. If expiresAt has a value, UpdateTokenValue("expires_at", expiresAt.ToString("o")). Then UpdateTokenValue id_token/access_token (BR-OIDC-12).
  5. NoResult (Failure null, Principal null) is not handled by any guard in this operation: step 1 only checks context.User.Identity.IsAuthenticated on the existing (pre-re-authentication) principal, and JwtAuthorizationCookieClaimService.UpdateJwtTokenValues independently re-authenticates the Cookies scheme and only checks info.Failure != null before reading tokens. An authenticated incoming context does not guarantee the separate AuthenticateAsync("Cookies") call that follows returns a non-NoResult outcome. The middleware always invokes the no-explicit-token overload, which forwards idToken: null, accessToken: null into id = idToken ?? info.Properties.GetTokenValue(OpenIdConnectParameterNames.IdToken); on NoResult, info.Properties is null, and ASP.NET Core 8's AuthenticationTokenExtensions.GetTokenValue calls ArgumentNullException.ThrowIfNull(properties) before any token lookup. A NoResult here therefore first fails with an ArgumentNullException on that properties read, not a NullReferenceException on principal.AddUpdateClaim — the principal is never reached. An explicit-token overload call that supplies both idToken and accessToken would instead skip both property reads and fail later, at principal.AddUpdateClaim, when principal itself is null. Either way this is an unhandled case, not a scenario the code actively guards against*.
  6. If info.Succeeded, RewriteAuthCookie(principal, properties) → SignInAsync(context, CookieAuthenticationDefaults.AuthenticationScheme, principal, properties) (BR-OIDC-12).
  7. Otherwise throw HttpContextAuthenticateException(info.Failure) (BR-OIDC-11).
  8. On failure: exception propagates to the error handler.
  9. await next(context).

Note: the cookie is re-issued on every authenticated request*. (performance and Set-Cookie churn implication).

Process flow

flowchart TD
    Start([Request arrives]) --> AuthCheck{User authenticated?}
    AuthCheck -->|No| Next[next context]
    AuthCheck -->|Yes| Reauth[AuthenticateAsync Cookies]
    Reauth --> FailCheck{info.Failure != null?}
    FailCheck -->|Yes| LogErr["Log Error, return info"]
    FailCheck -->|No| UpdateClaims[Update id_token/access_token/expires_at claims]
    LogErr --> Succeeded{info.Succeeded?}
    UpdateClaims --> Succeeded
    Succeeded -->|Yes| Resign[RewriteAuthCookie then SignInAsync Cookies]
    Succeeded -->|No| Throw[Throw HttpContextAuthenticateException]
    Resign --> Next
    Next --> Done([Pipeline continues])

Failure and edge-case behavior

  • A failed cookie re-authentication throws HttpContextAuthenticateException, which propagates to whatever error handler the host application registers — there is no built-in recovery path.
  • The cookie is re-signed on every authenticated request, regardless of whether any claim value actually changed*, which is a potential performance and response-size concern under load.
  • NoResult from AuthenticateAsync (both Failure and Principal null) is not handled: an authenticated incoming request (step 1) does not enforce a non-NoResult outcome from the separate Cookies-scheme re-authentication in UpdateJwtTokenValues. Because the middleware always calls the no-explicit-token overload, the first failure on NoResult is an ArgumentNullException thrown by AuthenticationTokenExtensions.GetTokenValue when it checks the null info.Properties, before principal.AddUpdateClaim is ever reached. This can arise, for example, when an authenticated principal was established independently of the registered Cookies scheme (such as a different authentication middleware populating context.User) while that scheme itself returns NoResult — a host-dependent scenario, not a missing or expired scheme registration, which does not itself produce NoResult.

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts. Statements marked with ** could not be confirmed; treat as unknown until verified.

Sources

  • src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Middleware/JwtClaimMiddleware.cs
  • src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/JwtAuthorizationCookieClaimService.cs
  • https://raw.githubusercontent.com/dotnet/aspnetcore/v8.0.0/src/Http/Authentication.Abstractions/src/TokenExtensions.cs (independent reference for AuthenticationTokenExtensions.GetTokenValue's null-properties check)