You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ThunderID currently signs OAuth2/OIDC access tokens (and ID tokens) with asymmetric keys only (RS256/RS512/PS256/ES256/ES384/ES512/EdDSA/ML-DSA-44/65/87); there is no symmetric/HMAC branch in the JWT signing path. Confidential clients that already share a secret with a trusted resource server cannot use that secret to sign/verify tokens, and instead must adopt asymmetric keys + JWKS distribution. This design adds HS256 (HMAC-SHA256) as a shared-secret signing option. HS256 is also post-quantum secure (not broken by Shor's algorithm), aligning with the Post-quantum-safe by Design pillar.
High-Level Approach
Add an HMAC signing branch (HS256) to the JWT signing/verification core alongside the existing asymmetric algorithms.
Resolve a symmetric signing secret when alg = HS256 is requested (per-client client_secret or a dedicated signing secret), rather than a PKI key reference.
Set the JWS header alg to HS256 and sign with HMAC-SHA256 over the signing input.
Enforce a minimum secret length (>= 256 bits) at configuration/validation time.
Advertise HS256 in the relevant discovery *_signing_alg_values_supported metadata only when a symmetric signing secret is configured.
Architecture Overview
Components touched:
JWT service (backend/internal/system/jose/jwt/service.go) — GenerateJWT / resolveSigningKey: accept HS256, route to an HMAC signer, resolve the symmetric secret instead of a PKI key ref.
Crypto core (backend/internal/system/cryptolib/sign.go) — add an HMAC case to SignAlgorithmFor/Generate/Verify (currently RSA/RSA-PSS/ECDSA/EdDSA/ML-DSA only).
Crypto provider (backend/internal/system/kmprovider/defaultkm/...) — expose HMAC sign/verify and surface HS256 from GetSupportedSigningAlgorithms when a secret is configured.
Token builder (backend/internal/oauth/oauth2/tokenservice/builder.go) — allow HS256 to be selected for the access-token build.
Discovery (backend/internal/oauth/oauth2/discovery/service.go) — reflect HS256 in the advertised algorithm sets.
Key decision: where the secret comes from (per-client client_secret vs. a dedicated symmetric signing secret) — see Questions below.
Security Considerations
Shared-secret trust model: HS256 tokens can only be verified by parties holding the secret. They are NOT suitable for third parties validating via JWKS. This must be documented and, ideally, the token audience constrained to holders of the secret.
Secret storage: symmetric signing secrets must be stored encrypted at rest and never exposed via JWKS or any public metadata.
Algorithm confusion: guard against the classic RS256/HS256 confusion attack — the verifier must pin the expected algorithm and never derive HMAC keys from public key material.
Post-quantum posture: HMAC-SHA256 is quantum-resistant (Grover only halves effective security; ~128-bit retained for a 256-bit secret), consistent with the product's post-quantum pillar.
Alternative 1: Keep asymmetric-only signing, require JWKS everywhere.
Pros: No new signing path; single trust model.
Cons: Heavy for the shared-secret use case; no parity with Auth0/Keycloak; forces PKI where a secret would do.
Decision: Insufficient for confidential-client + trusted-RS scenarios.
Alternative 2: Support the full HMAC family (HS256/HS384/HS512) now.
Pros: Completeness, matches Keycloak.
Cons: More surface for a first iteration.
Decision: Start with HS256, leave HS384/HS512 as a straightforward follow-up.
Alternative 3: Use a dedicated symmetric signing secret rather than reusing client_secret.
Pros: Separation of concerns; rotating the signing secret doesn't rotate client auth.
Cons: Extra configuration surface.
Decision: Open question below.
Questions for Community Input
Secret source: reuse per-client client_secret, or require a dedicated symmetric signing secret?
Scope: HS256 only, or the full HS256/HS384/HS512 family from the start?
Should HS256 be allowed for ID tokens as well as access tokens, or access tokens only initially?
Configuration granularity: per-client opt-in, or a server-level signing option?
How should we surface the shared-secret trust model to integrators to prevent misuse (e.g. tokens expected to be validated via JWKS)?
Precedent note: Auth0 and Keycloak support HS256 for token signing. Okta signs its access/ID tokens with RS256 only, so the precedent here is Auth0 and Keycloak.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Related Feature Issue
#5242
Problem Summary
ThunderID currently signs OAuth2/OIDC access tokens (and ID tokens) with asymmetric keys only (
RS256/RS512/PS256/ES256/ES384/ES512/EdDSA/ML-DSA-44/65/87); there is no symmetric/HMAC branch in the JWT signing path. Confidential clients that already share a secret with a trusted resource server cannot use that secret to sign/verify tokens, and instead must adopt asymmetric keys + JWKS distribution. This design addsHS256(HMAC-SHA256) as a shared-secret signing option. HS256 is also post-quantum secure (not broken by Shor's algorithm), aligning with the Post-quantum-safe by Design pillar.High-Level Approach
HS256) to the JWT signing/verification core alongside the existing asymmetric algorithms.alg = HS256is requested (per-clientclient_secretor a dedicated signing secret), rather than a PKI key reference.algtoHS256and sign with HMAC-SHA256 over the signing input.HS256in the relevant discovery*_signing_alg_values_supportedmetadata only when a symmetric signing secret is configured.Architecture Overview
Components touched:
backend/internal/system/jose/jwt/service.go) —GenerateJWT/resolveSigningKey: acceptHS256, route to an HMAC signer, resolve the symmetric secret instead of a PKI key ref.backend/internal/system/cryptolib/sign.go) — add an HMAC case toSignAlgorithmFor/Generate/Verify(currently RSA/RSA-PSS/ECDSA/EdDSA/ML-DSA only).backend/internal/system/kmprovider/defaultkm/...) — expose HMAC sign/verify and surfaceHS256fromGetSupportedSigningAlgorithmswhen a secret is configured.backend/internal/oauth/oauth2/tokenservice/builder.go) — allowHS256to be selected for the access-token build.backend/internal/oauth/oauth2/discovery/service.go) — reflectHS256in the advertised algorithm sets.Key decision: where the secret comes from (per-client
client_secretvs. a dedicated symmetric signing secret) — see Questions below.Security Considerations
Impacted Areas
Alternatives Considered
Alternative 1: Keep asymmetric-only signing, require JWKS everywhere.
Alternative 2: Support the full HMAC family (
HS256/HS384/HS512) now.HS256, leaveHS384/HS512as a straightforward follow-up.Alternative 3: Use a dedicated symmetric signing secret rather than reusing
client_secret.Questions for Community Input
client_secret, or require a dedicated symmetric signing secret?HS256only, or the fullHS256/HS384/HS512family from the start?Precedent note: Auth0 and Keycloak support HS256 for token signing. Okta signs its access/ID tokens with RS256 only, so the precedent here is Auth0 and Keycloak.
All reactions