---
title: JWT controller claim helpers
summary: JwtControllerBase derives username and authentication time from JWT claims.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.ServiceExtensions
reference_type: package
operation_id: OP-22
trigger: A controller derived from JwtControllerBase calls GetUsernameFromClaim() or GetAuthTimeFromClaim().
---

# JWT controller claim helpers

This operation provides two claim-derivation helpers on `JwtControllerBase`: deriving a display username from the `nameidentifier`-type claim, and parsing the authentication time from the `auth_time` claim.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-22 |
| **Trigger** | A controller derived from `JwtControllerBase` calls `GetUsernameFromClaim()` or `GetAuthTimeFromClaim()`. |
| **Inputs** | The current user's `ClaimsPrincipal` (via `HttpContext.User`). |
| **Outputs** | A derived username string, or a parsed `DateTime` authentication time. |
| **Primary components** | `JwtControllerBase.GetUsernameFromClaim`, `.GetAuthTimeFromClaim` |
| **Entry point** | `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/JwtControllerBase.cs` (L14–40) |

## Steps

1. `GetUsernameFromClaim`: find the first claim whose type contains `"nameidentifier"`, split its value on `@`, take index `[0]`, upper-case the result ([BR-SE-27](../business-rules.md#br-se-27-username-and-auth-time-are-derived-from-jwt-claims))
   - On exception during parsing: returns `"Unknown"`.
   - If the claim is missing: returns `null`.
2. `GetAuthTimeFromClaim`: find the `auth_time` claim and, only if `long.TryParse` succeeds on its value, parse it as Unix seconds since epoch via `DateTimeOffset.FromUnixTimeSeconds(unixTimestamp).UtcDateTime` ([BR-SE-27](../business-rules.md#br-se-27-username-and-auth-time-are-derived-from-jwt-claims))
   - If the claim is missing or its value is not a parseable `long`: returns `DateTime.MinValue` without throwing.
   - If the value parses as a `long` but is outside the range `DateTimeOffset.FromUnixTimeSeconds` supports: the method has no `try`/`catch`, so `ArgumentOutOfRangeException` propagates uncaught to the caller — this is a distinct third outcome from the two above.

## Process flow

This diagram shows the per-call branching inside each helper on a single invocation of `GetUsernameFromClaim`/`GetAuthTimeFromClaim`, not a combined or sequential call to both.

```mermaid
flowchart TD
    Start([GetUsernameFromClaim / GetAuthTimeFromClaim called]) --> S1{Which helper?}
    S1 -->|Username| S2{nameidentifier claim present?}
    S2 -->|No| EndNull([Return null])
    S2 -->|Yes| S3{Parsing succeeds?}
    S3 -->|Yes| EndName([Return upper-cased local part])
    S3 -->|No| EndUnknown(["Return 'Unknown'"])
    S1 -->|Auth time| S4{auth_time claim present and long.TryParse succeeds?}
    S4 -->|No| EndMin([Return DateTime.MinValue])
    S4 -->|Yes| S5{"Value in FromUnixTimeSeconds range?"}
    S5 -->|Yes| EndTime([Return parsed DateTime])
    S5 -->|No| EndThrow(["ArgumentOutOfRangeException propagates uncaught"])
```

## Failure and edge-case behavior
- Any exception while parsing the username claim yields `"Unknown"`; a missing claim yields `null` (these are distinct outcomes).
- A missing or non-numeric `auth_time` claim yields `DateTime.MinValue` rather than throwing.
- A numeric `auth_time` claim value outside the range `DateTimeOffset.FromUnixTimeSeconds` supports throws `ArgumentOutOfRangeException`, uncaught, from `GetAuthTimeFromClaim` — this method has no `try`/`catch` around the conversion, unlike `GetUsernameFromClaim`.

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

## Sources
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/JwtControllerBase.cs`
