Summary
Ship a built-in WebAuthn step-up primitive so consumers get step-up re-authentication out of the box, rather than having to implement the StepUpService SPI themselves.
Background
The security concern from SUF-02 (#328) is already closed: POST /user/setPassword now requires a StepUpService when one is provided and is disabled by default otherwise (shipped in #334). This ticket is the enhancement — a default, framework-provided step-up implementation — not a security gap.
Spring Security provides the WebAuthn login assertion flow (/webauthn/authenticate/options, /login/webauthn), but not a step-up assertion (prove presence while already authenticated, bound to a specific action). That flow is what this ticket builds.
Proposed design (to be refined)
- Challenge endpoint —
POST /user/stepup/webauthn/options (authenticated): generate a PublicKeyCredentialRequestOptions (allowCredentials = the user's registered passkeys) and store a challenge server-side bound to {userId, action, rpId, origin, expiry (short, e.g. 2 min)} — session attribute or a short-lived table.
- Verification — the client performs
navigator.credentials.get(...) and posts the assertion to POST /user/stepup/webauthn (or includes it on the target request). A default StepUpService bean verifies the assertion against the stored challenge via Spring Security's WebAuthnRelyingPartyOperations, checks binding + expiry, and marks step-up satisfied (e.g. a short-lived session flag keyed by action).
- Wiring —
setPassword (and any future credential-altering ops) already consult StepUpService; the default bean plugs in via @ConditionalOnMissingBean(StepUpService.class) so a consumer override still wins.
- Config — enable/disable flag; challenge TTL; which actions require step-up.
- Fallbacks — for users without a passkey (e.g. OAuth-only), decide: no default step-up available (endpoint stays disabled / consumer must supply their own), or an alternative factor.
Open questions
- Challenge storage: session vs short-lived DB table (multi-node)?
- Single-use step-up token vs session flag; binding granularity per action.
- Client contract: separate verify endpoint vs assertion carried on the target request.
- Docs + a reference frontend snippet in the demo app.
Scope / effort
Feature-scale (new endpoints, challenge lifecycle, verification, tests, docs, demo wiring). Warrants its own design pass before implementation.
References
Summary
Ship a built-in WebAuthn step-up primitive so consumers get step-up re-authentication out of the box, rather than having to implement the
StepUpServiceSPI themselves.Background
The security concern from SUF-02 (#328) is already closed:
POST /user/setPasswordnow requires aStepUpServicewhen one is provided and is disabled by default otherwise (shipped in #334). This ticket is the enhancement — a default, framework-provided step-up implementation — not a security gap.Spring Security provides the WebAuthn login assertion flow (
/webauthn/authenticate/options,/login/webauthn), but not a step-up assertion (prove presence while already authenticated, bound to a specific action). That flow is what this ticket builds.Proposed design (to be refined)
POST /user/stepup/webauthn/options(authenticated): generate aPublicKeyCredentialRequestOptions(allowCredentials = the user's registered passkeys) and store a challenge server-side bound to{userId, action, rpId, origin, expiry (short, e.g. 2 min)}— session attribute or a short-lived table.navigator.credentials.get(...)and posts the assertion toPOST /user/stepup/webauthn(or includes it on the target request). A defaultStepUpServicebean verifies the assertion against the stored challenge via Spring Security'sWebAuthnRelyingPartyOperations, checks binding + expiry, and marks step-up satisfied (e.g. a short-lived session flag keyed by action).setPassword(and any future credential-altering ops) already consultStepUpService; the default bean plugs in via@ConditionalOnMissingBean(StepUpService.class)so a consumer override still wins.Open questions
Scope / effort
Feature-scale (new endpoints, challenge lifecycle, verification, tests, docs, demo wiring). Warrants its own design pass before implementation.
References
StepUpService,user.security.allowInitialPasswordSetWithoutStepUp.