---
title: Synchronize JWT values into claims and cookie
summary: Keeps id_token/access_token claims current and re-signs the auth cookie on every authenticated request.
status: draft
owner: SAIF Framework Platform Team
last_reviewed: 2026-10-02
library: SAIF.NetCore.OIDC
reference_type: package
operation_id: OP-05
trigger: JwtClaimMiddleware.InvokeAsync, every request
---

# 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](../business-rules.md#br-oidc-11-a-failed-cookie-re-authentication-aborts-the-request)).
2. `info = UpdateJwtTokenValues(context)`, which calls `AuthenticateAsync("Cookies")`.
   - On failure: if `info.Failure != null`, log Error and return `info`.
3. 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](../business-rules.md#br-oidc-12-jwt-claims-and-the-cookie-are-refreshed-on-every-request)).
   - `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*.
4. If `info.Succeeded`, `RewriteAuthCookie(principal, properties)` → `SignInAsync(context, CookieAuthenticationDefaults.AuthenticationScheme, principal, properties)` ([BR-OIDC-12](../business-rules.md#br-oidc-12-jwt-claims-and-the-cookie-are-refreshed-on-every-request)).
5. Otherwise throw `HttpContextAuthenticateException(info.Failure)` ([BR-OIDC-11](../business-rules.md#br-oidc-11-a-failed-cookie-re-authentication-aborts-the-request)).
   - On failure: exception propagates to the error handler.
6. `await next(context)`.

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

## Process flow

```mermaid
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`.

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

> **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)
