Skip to content

docs: note WebAuthn enrollment denial changed from 403 to 401+step-up-required in 5.3.4 - #373

Merged
devondragon merged 1 commit into
mainfrom
docs/webauthn-enrollment-denial-401-note
Aug 22, 2026
Merged

docs: note WebAuthn enrollment denial changed from 403 to 401+step-up-required in 5.3.4#373
devondragon merged 1 commit into
mainfrom
docs/webauthn-enrollment-denial-401-note

Conversation

@devondragon

Copy link
Copy Markdown
Owner

Summary

Adds a missing migration note for a consumer-facing behavior change introduced by #371.

#371 changed the stale/factorless-session denial on POST /webauthn/register from a bare HTTP 403 (the filter-chain authorization rule's default, no interpretable body) to HTTP 401 with a JSON body {"message": ..., "error": "step-up-required"}, via the new StepUpEnrollmentAccessDeniedHandler. This matches the passkey delete/rename endpoints so a client can recognize the denial and prompt for a fresh sign-in. The change shipped without a migration note.

This adds a 5.3.3 → 5.3.4 callout to the enrollment-gating section of MIGRATION.md telling clients that branched on the old 403 to also treat a 401 with error code step-up-required as the stale-session case.

How this was found

Surfaced by the pre-release demo-app release-integration test: the demo's chromium-step-up Playwright suite failed because webauthn-register.js branched on the old 403 and fell back to a generic message. The demo-side fix is in a companion PR on the demo repo; both now pass (step-up suite 8/8).

Docs-only change.

https://claude.ai/code/session_01KL5qvKVGQLLQDjHToy34vj

…-required in 5.3.4

#371 changed the stale/factorless-session denial on POST /webauthn/register from a
bare HTTP 403 (the filter-chain authorization rule's default) to HTTP 401 with a JSON
body carrying error code "step-up-required", matching the passkey delete/rename
endpoints so a client can interpret it. That is a consumer-facing response-contract
change with no migration note; a client that branched on the 403 for this endpoint
(as the demo app's webauthn-register.js did) silently falls back to a generic error.

Add a 5.3.3 -> 5.3.4 callout to the enrollment-gating section directing such clients
to also treat a 401 with error code step-up-required as the stale-session case.

Surfaced by the pre-release demo-app integration test (chromium-step-up E2E).

Claude-Session: https://claude.ai/code/session_01KL5qvKVGQLLQDjHToy34vj
Copilot AI lite review requested due to automatic review settings August 22, 2026 01:07
@devondragon
devondragon merged commit 5d2e30a into main Aug 22, 2026
4 checks passed
@devondragon
devondragon deleted the docs/webauthn-enrollment-denial-401-note branch August 22, 2026 01:08
@claude

claude Bot commented Aug 22, 2026

Copy link
Copy Markdown

Review

Small, well-scoped docs-only change — verified the technical claims against the actual code and they check out.

Verified correct:

  • StepUpEnrollmentAccessDeniedHandler (src/main/java/.../security/StepUpEnrollmentAccessDeniedHandler.java) does return HTTP 401 with Content-Type: application/json and body {"message": ..., "error": "step-up-required"} — matches the note.
  • WebAuthnStepUpRequiredException.ERROR_CODE is indeed "step-up-required".
  • The prior behavior (bare 403 from the filter-chain authorization rule) is consistent with the handler's own Javadoc, which explicitly frames the change as "rather than receiving a bare 403 it cannot interpret."
  • gradle.properties currently shows version=5.3.4-SNAPSHOT, so the 5.3.3 → 5.3.4 version callout is accurate for the release this will ship in.

Style/placement:

  • Follows the existing convention used elsewhere in MIGRATION.md (e.g. the 5.0.0 → 5.0.1 blockquote note a few paragraphs above) — same format, placed immediately after the paragraph it clarifies, so readers get the update in context rather than in a separate changelog-only section. Good consistency.
  • The closing sentence ("Re-running the passkey ceremony cannot satisfy enrollment...") is a nice touch — it heads off the natural but wrong assumption that retrying the WebAuthn ceremony would resolve a 401 here, same as it would for the credential-management endpoints.

Minor nitpick (non-blocking): the note says the old response had "no interpretable body," which is accurate for the default Spring Security 403 (empty body) — just flagging that if any consuming app previously added a custom AccessDeniedHandler for that specific rule, this note doesn't cover that edge case, but that's an extremely unlikely customization to have made on an internal filter-chain default and not worth mentioning in the doc.

No code, tests, or security surface changed — nothing further to flag. LGTM.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new migration note’s JSON example uses ... (invalid JSON) and should be updated to a valid placeholder format to avoid clients copying an incorrect payload.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a missing migration note documenting a consumer-facing behavior change in the WebAuthn enrollment endpoint denial response, so client apps can correctly detect the “stale session / step-up required” case when upgrading.

Changes:

  • Document that POST /webauthn/register stale/factorless-session denial changed from bare HTTP 403 to HTTP 401 with a step-up-required JSON error contract in 5.3.4.
  • Provide client guidance to branch on the new 401 + error=step-up-required condition.
File summaries
File Description
MIGRATION.md Adds a 5.3.3 → 5.3.4 callout describing the updated enrollment denial response contract for clients.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread MIGRATION.md

**Passkey enrollment is gated when step-up is enabled.** `POST /webauthn/register` now requires an authentication factor issued within `user.security.stepUp.enrollmentTtlSeconds` (default `600`). Without it the feature protected nothing: an attacker holding a session cookie could enroll their own passkey and assert with it to satisfy every other gate. Any factor counts, not the configured `factors` list, so a first passkey can still be registered after an ordinary password or social login.

> **5.3.3 → 5.3.4 note — enrollment denial response.** Through 5.3.3 the stale/factorless-session denial on `POST /webauthn/register` was a bare `HTTP 403` (the filter-chain authorization rule's default, with no interpretable body). As of 5.3.4 it is `HTTP 401` with a JSON body `{"message": ..., "error": "step-up-required"}`, matching the passkey delete/rename endpoints so a client can recognize it and prompt for a fresh sign-in instead of a passkey retry. If you wrote a client that branched on the `403` for this endpoint, update it to also treat a `401` with error code `step-up-required` as the stale-session case. Re-running the passkey ceremony cannot satisfy enrollment (it accepts any factor and the account may have no passkey yet), so the remedy is a fresh login.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants