fix(security): first-party JwtBearer with HS256 pinned; /auth/token mints for a cookie session only

The hand-rolled bearer handler is replaced by AddJwtBearer (Microsoft.AspNetCore.Authentication.JwtBearer
10.0.7, pinned on the same servicing line as the other AspNetCore packages). Validation parameters come
from JwtTokenService.BuildValidationParameters, which now pins ValidAlgorithms = HS256 for both the
bearer scheme and TryValidate; MapInboundClaims is off so the minted Role/zb:username keys resolve.
/auth/token requires the cookie scheme, so a bearer can no longer mint a fresh token for itself.

Refs #733
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
Joseph Doherty
2026-10-22 13:35:54 -04:00
parent 4d17b346c6
commit 3ddb6876db
10 changed files with 187 additions and 126 deletions
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
@@ -108,8 +109,21 @@ public static class ServiceCollectionExtensions
});
if (jwtConfigured)
{
authBuilder.AddScheme<AuthenticationSchemeOptions, JwtBearerAuthenticationHandler>(
OtOpcUaAuthSchemes.Bearer, "OtOpcUa bearer token", _ => { });
// First-party JwtBearer (R03:2.4, fix round 1 / I1) — the earlier hand-rolled handler is gone.
// Validation parameters come from the SAME JwtTokenService that mints, so issuer/audience/key/
// claim types can never drift; BuildValidationParameters pins ValidAlgorithms = HS256 (an
// alg:none or a different-HMAC token is refused). MapInboundClaims=false keeps the minted
// "Role"/"zb:username" claim keys verbatim, which RoleClaimType/NameClaimType name. A bad token
// is AuthenticateResult.Fail ⇒ 401 + WWW-Authenticate: Bearer, never a 500.
authBuilder.AddJwtBearer(OtOpcUaAuthSchemes.Bearer, "OtOpcUa bearer token", _ => { });
services.AddOptions<JwtBearerOptions>(OtOpcUaAuthSchemes.Bearer)
.Configure<JwtTokenService>((o, jwt) =>
{
o.TokenValidationParameters = jwt.BuildValidationParameters();
o.MapInboundClaims = false;
o.SaveToken = false;
o.IncludeErrorDetails = false;
});
}
if (disableLogin)
{