00.77.00
.oauth2(...) bootstrap, built on the 00.76 jSentinel-jwt
foundation. Additive — three new opt-in modules; every 00.74–00.76 app
compiles and boots identically. No migration. The current release is
00.79.41 (OIDC followed in
00.78.00).Release date: 2026-06-26 Previous release: 00.76.10 Maven coordinates (parent):
com.svenruppert.jsentinel:jSentinel-parent:00.77.00Full changelog: GitHub release v00.77.00
Headline — the full OAuth2 RP protocol surface
Three new modules (jSentinel-oauth2, jSentinel-oauth2-vaadin,
jSentinel-oauth2-rest) implement the Relying-Party side of:
- Authorization-Code flow + PKCE (RFC 6749 / 7636) — builds the S256 authorization URI, binds a single-use CSPRNG
state+ PKCE verifier in aStateStore, and exchanges the code on callback. PKCE is S256-only —plainis unrepresentable. - Token endpoint + client authentication (RFC 6749 / 7523) —
client_secret_basic,client_secret_post,none(public + PKCE),private_key_jwtandclient_secret_jwt. JWT client assertions are built and signed via the newJwtSignerSPI (resolved throughServiceLoader, sojSentinel-oauth2never imports a JOSE library). - Refresh-token rotation with reuse-detection (RFC 9700 §4.13.2) — a replayed (already-rotated) refresh token revokes the whole family; only token hashes are stored.
- Introspection (RFC 7662) — a bounded, TTL’d, SHA-256-keyed decision cache.
- Revocation (RFC 7009).
- Device Authorization Grant (RFC 8628) — with
interval/slow_downback-off handling;demo-standaloneships a browserless device-code login reference.
New: JWT signing (JwtSigner)
00.76 only validated JWTs; signing client assertions needs the other
direction. New JOSE-free JwtSigner + JwtSigningKey SPIs in jSentinel-core,
with a Nimbus-backed NimbusJwtSigner in jSentinel-jwt (RSA/EC via Nimbus,
EdDSA via the JDK). The signing key is masked in toString() and validated
against the algorithm family.
Bootstrap — .oauth2(...)
A declarative .oauth2(...) sub-builder on all three adapter facades. The DX
layer only records + STRICT-validates the RP configuration (codes such as
oauth2/missing-client-id, oauth2/missing-token-endpoint,
oauth2/redirect-uri-not-https); the HTTP clients are assembled adapter-side,
so jSentinel-dx stays free of any JOSE / HTTP-client compile dependency.
JSentinelDiagnostics.inspect() gains a non-secret [OAuth2] contribution
(never a client id, endpoint or secret).
Security properties
HTTPS enforced on every endpoint (a http://localhost* carve-out only with
-Djsentinel.dev=true); response bodies capped at 1 MiB; non-2xx never echoes
the server’s error_description; opaque tokens never logged or rendered;
client-secret byte arrays zeroed after use; PKCE S256-only; refresh
reuse-detection revokes the family before a replayed token is forwarded;
client assertions carry a fresh 128-bit jti, a bounded exp and the endpoint
aud. Seven new non-secret JSentinel-events types (OAuth2TokenObtained /
Failed, RefreshTokenReuseDetected, the device-grant events, OAuth2StateInvalid).
A multi-reviewer exit pass fixed four findings in-cycle — most notably
reuse-detection now runs before the AS call (R-EXIT-1, High) and a
documented integrator requirement that a REST integrator MUST bind state to
the caller’s session (R-EXIT-4); the Vaadin VaadinSessionStateStore does this
automatically.
Quality
Full reactor (43 modules) clean install green; -Pstatic-analysis verify
(Checkstyle + SpotBugs/FindSecBugs) green on every touched module. No mocks
throughout — every flow tested against a real JDK HttpServer with real crypto
(RSA/EC keypairs, HMAC, SHA-256): token exchange, PKCE + single-use state,
refresh rotation + reuse, introspection cache, client-assertion signatures and
the device-grant poll loop. jSentinel-oauth2 first PIT pass: 62 % (184/299)
mutation / 80 % line; a targeted lift is a 00.77.x follow-up.
What it does not do
No OIDC ID-token / nonce validation or full OIDC-RP login (00.78), no
Stable-API promotion — all 00.77 public types are @ExperimentalJSentinelApi
(promotion no earlier than 00.79).
Migration
None. The OAuth2 RP is opt-in; apps that don’t call .oauth2(...) pull no
new dependency. Just bump the version to 00.77.00.