Phase 3.A — OIDC algorithm whitelist (consumer migration)
Phase 3.A — OIDC algorithm whitelist (consumer migration)
What changes. OidcAuthProvider widens its accepted JWS signature set beyond RS256. RS384, RS512, ES256, and PS256 are now first-class verify paths, gated behind an operator-chosen whitelist on AuthConfig. Every deployment that signed RS256 yesterday continues to validate RS256 today, byte-for-byte unchanged — the new field defaults to None, which resolves to the historical single-algorithm trust set [RS256].
Scope. Server-side only — no client-side change. OidcClient (browser sign-in) does not look at signature algorithms; algorithm selection is the issuer's choice, surfaced to the server verifier through the alg field on each issued token.
What was added
JwsAlgorithmDU inToolUp.Platform.Core—RS256 | RS384 | RS512 | ES256 | PS256.HS256is deliberately not a member: symmetric flows belong toStaticJwtAuthProvider, not OIDC. EdDSA / Ed25519 are deferred until customer demand surfaces.AuthConfig.AcceptedAlgorithms: JwsAlgorithm list option— operator-owned trust set.Noneresolves to[RS256].OidcAuthProvidernow dispatches signature verification on the typed algorithm; RSA / EC JWKS key materialisation is handled by the sameOidcAuthProvider.Jwkscode path that previously only understood RSA.
When to opt in
Reach for AcceptedAlgorithms = Some [...] when:
- AWS Cognito — User Pool tokens default to RS256 today, but the OIDC OP for Cognito Identity Pools and the App Client variants for federated identity providers can emit ES256.
- Firebase Auth — federated sign-in (Google / Apple / Facebook) frequently rotates between RS256 and ES256 on the same issuer.
- Okta / dynamic-client OIDC flows — when the client registers a JWKS rather than a static signing key, the issuer may emit any algorithm announced in
id_token_signing_alg_values_supported. - Hardened RSA deployments — sites that ban PKCS#1 v1.5 padding by policy ship PS256 instead of RS256.
If you're on Entra External ID, Auth0 (default), Azure AD workforce, Keycloak, or Google Workspace — all of which sign RS256 unless explicitly reconfigured — leave AcceptedAlgorithms unset. The default is what you want.
Diff to apply
Existing OIDC deployments wanting to widen the trust set:
// Before — RS256-only (the default; nothing in the record changes):
let authConfig = {
Issuer = Some issuerUrl
Audience = Some audience
KeySource = JwksDiscovery issuerUrl
TokenLocation = BearerHeader
ClockSkewSeconds = None
AcceptedAlgorithms = None // ← was missing pre-3.A; now explicit
}
// After — interoperate with an IdP that issues ES256 alongside RS256:
let authConfig = {
Issuer = Some issuerUrl
Audience = Some audience
KeySource = JwksDiscovery issuerUrl
TokenLocation = BearerHeader
ClockSkewSeconds = None
AcceptedAlgorithms = Some [ RS256; ES256 ]
}
Consumers that hand-roll their AuthConfig record literal (as opposed to using EntraExternalIdAuthProvider.fromEnv or similar helpers) must add the new AcceptedAlgorithms field on the record literal — the compiler will flag the missing field as a record-completeness error. Set it to None for byte-for-byte backward compatibility.
Security rationale
The whitelist is operator-owned, not auto-widened by the SDK. Even though every recognised algorithm carries its own cryptographic verify path, the SDK does not silently trust a new algorithm just because the inbound token's alg field names one. Three reasons:
- Algorithm-substitution attacks. An issuer that today signs RS256 may, after a misconfiguration, start emitting tokens with a different
algwhile the SDK keeps fetching the same JWKS. The whitelist makes that drift visible as aUnsupported algorithm: <name>rejection rather than silently accepting the new shape. - Defence in depth. A compromised JWKS endpoint that injects an attacker-controlled EC key into the response cannot succeed against an
AcceptedAlgorithms = Some [RS256]deployment — the algorithm gate rejects the ES256 token before the verify even runs. - Auditability. Operators can answer "what algorithms is this deployment willing to trust?" by reading a single line of config, not by reading SDK source. Widening the trust set is an explicit code change reviewable on its own.
Verification
dotnet buildclean across the workspace.- The Expecto runner
dotnet run --project src/ToolUp.Platform.Testspasses allAuthProviderstests, including the five new Phase 3.A cases (Default config accepts RS256 only, the four per-algorithm Ok tests, and the ES256-against-RS256-whitelist rejection). - Operationally: start the deployment with
AcceptedAlgorithms = None; confirm RS256 tokens from the existing IdP validate as before. Then setSome [ RS256; ES256 ], restart, and confirm an ES256-signed token from a Cognito-shaped IdP validates while the RS256 path continues to work. - The
MockOidcServer-driven contract suite continues to pass — that mock issuer signs RS256, so the path under the default whitelist is unchanged.
Rollback
If a regression surfaces, set AcceptedAlgorithms = None (or omit the field — the compiler will demand it on the record literal, but None is the documented default). The provider falls back to the historical RS256-only path. The change is fully reversible at the deployment level; no on-disk state is migrated.
Consumers
The migration is N-A for any consumer whose IdP issues RS256 tokens (the common case) — they inherit the SDK default. A consumer becomes affected only if and when its IdP migrates to a non-RS256 algorithm. The SDK-side change is consumer-invisible until the consumer explicitly chooses to widen its trust set.