v00.78.00 — OpenID Connect Relying-Party
Release Notes — V00.78.00
jSentinel V00.78.00 — OpenID Connect Relying-Party. The identity-federation
release: it layers OIDC identity (ID-token validation, discovery, UserInfo,
RP-initiated logout, claims-to-subject mapping) on the V00.76 JWT and V00.77 OAuth2
building blocks. Three new opt-in modules; all new public types are
@ExperimentalJSentinelApi (a coordinated stable-promotion of V00.76 + V00.77 +
V00.78 is planned for V00.79).
Headline — full OIDC RP protocol surface
Three new modules (jSentinel-identity-oidc, jSentinel-identity-oidc-vaadin,
jSentinel-identity-oidc-rest) implement the Relying-Party identity layer:
- ID-token validation (OIDC Core §3.1.3.7) —
DefaultIdTokenValidator
composes the V00.76JwtValidatorSPI (it does not subtype thefinal
Nimbus impl — see A.0 reconciliation), running JWT-standard validation first,
thennonce,azp(required on multi-audience),at_hash/c_hash
(§3.1.3.6 left-half digest selected by the JWS alg, constant-time compared),
auth_timevsmax_age, andacr. No JOSE import; hashes via the JDK
MessageDigest. - Discovery (OIDC Discovery 1.0 / RFC 8414) —
HttpOidcDiscoveryClientfetches
<issuer>/.well-known/openid-configuration, rejects a document whoseissuer
≠ the requested issuer (§4.3 substitution defence), and TTL-caches. Backed by a
strict in-tree JSON parser (OidcJson) — the module bans Jackson / Gson /
org.json. - UserInfo (OIDC Core §5.3) —
HttpUserInfoClient(Bearer GET, https-enforced,
1 MiB cap). The §5.3.2sub-match requirement is enforced in the claims mapper. - RP-Initiated Logout (OIDC RP-Initiated Logout 1.0) —
RpInitiatedLogoutInitiator
builds theend_session_endpointURL (id_token_hint+post_logout_redirect_uristate, percent-encoded).
- Claims-to-subject mapping —
DefaultClaimsToSubjectMapperbuilds the 4-field
stableJSentinelSubjectwith an issuer-prefixed, injective subject id; roles /
permissions come from dedicated mappers (empty by default — vendor mappings are
V00.79); tenant is a separateClaimsToTenantMapperSPI. @OidcAuthenticated— annotation +OidcAuthenticatedEvaluatorover the V00.70
annotation scanner: requires an authenticated subject and, givenacrValues, maps
an insufficientacrtoAuthorizationDecision.StepUpRequired(hardware > MFA >
strong), tying jSentinel step-up to OIDCacr_values.
A.0 reconciliation with shipped V00.77
The concept was written while V00.77 was in flight; the A.0 review-gate found and
corrected two assumptions against the shipped code, both without touching stable
types:
NimbusJwtValidatorisfinal→ the ID-token validator composes the
JwtValidatorSPI instead of subtyping it (cleaner; stays JOSE-isolated).JSentinelSubjectis the 4-field stable record (V00.73) → the mapper builds that;
tenant comes fromClaimsToTenantMapperand the OIDC context claims
(acr/amr/auth_time/…) stay onValidatedIdToken, not folded into the subject.
Bootstrap — .oidc(...)
CommonJSentinelBootstrap gains a declarative .oidc(...) sub-builder (all three
adapter facades inherit it). The DX layer only records + STRICT-validates,
referencing only the JOSE-free oidc/api + oauth2/api core types — no JOSE / HTTP
compile dependency. STRICT codes (Konzept §11.4):
| Code | Trigger |
|---|---|
oidc/missing-issuer |
.oidc(...) without .issuer(...) |
oidc/missing-client-id |
no .clientId(...) |
oidc/scope-without-openid |
scope set lacks openid (spec violation) |
oidc/redirect-uri-not-https |
redirect URI not https / not http://localhost* |
oidc/logout-without-post-logout-redirect-uri |
logoutEnabled(true) without a post-logout redirect |
JSentinelDiagnostics.inspect() gains an OidcDiagnosticContributor (sourced from a
non-secret boolean snapshot) that warns when the nonce check is disabled.
Events
Four new event types in jSentinel-events (non-secret — issuer / stable error code
only, never a token or claim): IdTokenValidated, IdTokenValidationFailed,
OidcLoginSucceeded, OidcLogout.
Security properties
ID-token signature via the V00.76 allow-list (no alg:none); nonce required by
default + single-use + constant-time compared; at_hash/c_hash per §3.1.3.6;
azp enforced on multi-audience; auth_time/max_age → step-up; discovery rejects
an issuer-substituted document; https enforced on every endpoint (dev-loopback only
with -Djsentinel.dev); the access token / id_token_hint live only in headers /
encoded params and are never logged (masked in toString); response bodies capped
at 1 MiB; the in-tree JSON parser is depth-capped and routes all malformed input
through the Result channel; subject ids are issuer-prefixed + injective.
Exit-review hardening (R-EXIT-1..4)
A three-reviewer adversarial exit pass confirmed the ID-token validation is
spec-faithful and produced four fixes:
- R-EXIT-1 (MEDIUM) — the issuer-prefixed subject id
iss#subwas non-injective
(submay contain#); both components are now%/#-escaped so distinct
(iss,sub)pairs cannot collide on the primary identity key. - R-EXIT-2 (MEDIUM) — the claims mapper now rejects a UserInfo response whose
subdiffers from the ID token'ssub(OIDC §5.3.2 identity-substitution defence). - R-EXIT-3 (LOW-MED) —
OidcJsonvalidates\uXXXXhex itself and throws
JsonExceptioninstead of letting aNumberFormatExceptionescape theResult
channel on an attacker-controlled body. - R-EXIT-4 (LOW) — the
noncecompare is now constant-time and rejects a missing
nonceclaim explicitly.
Composition contract (documented): the JwtValidator composed by
DefaultIdTokenValidator MUST be configured with the RP's client_id as its
accepted audience — the OIDC layer relies on the JWT layer for the single-audience
aud check (the .oidc(...) bootstrap wires this).
Module / dependency rules
jSentinel-identity-oidc → jSentinel-core, jSentinel-jwt, jSentinel-oauth2, jSentinel-events, jSentinel-dx. jSentinel-identity-oidc-vaadin → jSentinel-vaadin, jSentinel-oauth2-vaadin (+ provided jakarta.servlet-api).
jSentinel-identity-oidc-rest → jSentinel-rest, jSentinel-oauth2-rest. jSentinel-dx
references only the JOSE-free oidc/api + oauth2/api types in jSentinel-core.
Quality gates
- Full reactor (46 modules)
./mvnw clean installgreen. -Pstatic-analysis verify(Checkstyle + SpotBugs/FindSecBugs) green on every
touched module.- No-mocks throughout: the ID-token validator runs a real Nimbus
JwtValidator
over a real RSA JWKS key against real Nimbus-signed ID tokens; discovery / UserInfo
/ logout-handler run against real JDKHttpServerround-trips.
Mutation coverage (V00.78)
| Module | V00.78 mutation coverage | Line |
|---|---|---|
jSentinel-identity-oidc |
72 % (170/235) | 84 % (332/395) |
First PIT pass on the new module; surviving mutations concentrate in HTTP/IO error
branches and the JSON-parser edge cases. The security-critical ID-token validation
paths (nonce / azp / at_hash / auth_time / acr) are well covered by the no-mock
matrix. The touched V00.77 modules (jSentinel-core, jSentinel-dx,
jSentinel-events) retain their prior baselines — their V00.78 additions are
additive records / SPIs covered by the new tests.
Known limitations / follow-ups
- The full
demo-vaadinKeycloak login needs a running external IdP and is a
documented follow-up (as with the V00.77 Keycloak demo); the RP mechanics are
proven no-mock by the id-token / discovery / userinfo / callback / logout tests. - Vendor claim/role profiles (Keycloak
realm_access, Entrawids, Auth0
namespaces), hybrid-flowc_hashactivation, front-channel logout and
session-management are V00.79+. - A signed (
application/jwt) UserInfo response is a V00.78.x extension; V00.78
handles the JSON UserInfo response. - All V00.78 public types are
@ExperimentalJSentinelApi.
Out of scope (→ later)
OIDC vendor profiles + hybrid flow (V00.79), and any stable-API promotion (the
coordinated V00.76+77+78 promotion is V00.79).