dotnet / dotnet/aspnetcore

OpenIdConnectHandler does not validate auth_time against configured MaxAge on the callback

Open
#67,462 2 comments 0 reactions 0 assignees View on GitHub
area-auth investigate
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Summary

`OpenIdConnectHandler` sends the configured `MaxAge` as the `max_age` parameter on the outbound authorization request, but it never validates the `auth_time` claim of the received `id_token` against that value on the callback path. As a result, the `max_age` directive is effectively request-only: the handler relies entirely on the identity provider to honor it and performs no local check of its own.

This is raised as a **defense-in-depth / spec-compliance** improvement.

### Details

- On challenge, `OpenIdConnectHandler.HandleChallengeAsync` reads `MaxAge` (from challenge properties or `Options.MaxAge`) and writes `message.MaxAge` on the outbound request.
- On the callback, `HandleRemoteAuthenticateAsync` validates the token and protocol details but contains no reference to `auth_time` or `Options.MaxAge`. A workspace-wide search for `auth_time` / `AuthTime` across the OpenID Connect handler source returns no matches.

OpenID Connect Core 1.0 describes a corresponding client-side check:

> §3.1.3.7 (ID Token Validation), item 13: "If the `auth_time` Claim was requested, either through a specific request for this Claim or by using the `max_age` parameter, the Client SHOULD check the `auth_time` Claim value and request re-authentication if it determines too much time has elapsed since the last End-User authentication."

The handler currently does not implement this client-side check.

Relevant spec references:

- `max_age` — [OpenID Connect Core 1.0 §3.1.2.1](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) (when `max_age` is used, the returned ID Token MUST include `auth_time`).
- `auth_time` — [OpenID Connect Core 1.0 §2](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) (REQUIRED when `max_age` is requested; seconds since Unix epoch, UTC).
- RP check — [OpenID Connect Core 1.0 §3.1.3.7](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation).

### Affected source

- `src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs`

### Hints toward a fix

- Add an opt-in option (e.g. `OpenIdConnectOptions.EnforceMaxAge`, default `false`) so existing behavior is preserved unless the check is requested.
- Carry the effective `max_age` (in seconds) through the round trip. Note that `OpenIdConnectChallengeProperties.MaxAge` is stored in `AuthenticationProperties.Parameters`, which is not serialized into `state`; persisting the effective value into `AuthenticationProperties.Items` (which is serialized, like the existing PKCE `CodeVerifierKey`) makes the per-challenge value available on the callback.
- After `id_token` validation (covering both the implicit/hybrid `id_token` and the authorization-code/token-endpoint `id_token`), compare `auth_time` against the requested `max_age`:
- Read `auth_time` via `JwtSecurityToken.Payload.AuthTime` (`int?`, seconds since epoch).
- Use `TimeProvider.GetUtcNow()` for the current time.
- Allow tolerance via the existing `TokenValidationParameters.ClockSkew`.
- Treat a missing `auth_time` as a failure when `max_age` was requested (per §2 it is REQUIRED in that case).
- Surface a failure through the existing `HandleRequestResult.Fail(...)` path so it flows through the `RemoteFailure` event.

For reference, `OpenIdConnectProtocolValidator.RequireAuthTime` only checks for the *presence* of `auth_time`; it does not compare elapsed time against `max_age`, so enabling it alone does not implement the §3.1.3.7 check.

Contributor guide

Open the contributing guide

Research direction

Start in src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs, reading HandleChallengeAsync and HandleRemoteAuthenticateAsync, then inspect how CodeVerifierKey persists challenge state. Trace both implicit/hybrid and authorization-code ID-token validation paths. Done means an opt-in MaxAge check compares auth_time using the configured clock skew, rejects missing or expired values through HandleRequestResult.Fail, and preserves existing behavior when disabled.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
authentication, security
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.