Skip to content

feat: add a signed challenge store that keeps no server state - #157

Closed
RenzoMinelli wants to merge 13 commits into
masterfrom
rm--signed-challenge-store
Closed

RenzoMinelli wants to merge 13 commits into
masterfrom
rm--signed-challenge-store

Conversation

@RenzoMinelli

@RenzoMinelli RenzoMinelli commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

What: Adds ChallengeStores::Signed (:signed). The server signs each challenge with secret_key_base. The client returns it in clientDataJSON, and the server checks the signature. write now returns the challenge the options controllers send.

Why: :cache needs a cache shared by all servers. :signed keeps no server state and needs nothing extra from the client. Stacked on #154.

Trade-off: A signed challenge can be used more than once until it expires, like a challenge in a cookie session. :cache stays for apps that need single use.

How to test: bundle exec rspec spec/devise/webauthn/challenge_stores/signed_spec.rb spec/requests/devise/api_client_spec.rb.

@RenzoMinelli
RenzoMinelli marked this pull request as draft October 7, 2026 21:50
RenzoMinelli added a commit that referenced this pull request Oct 7, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RenzoMinelli added a commit that referenced this pull request Oct 8, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RenzoMinelli
RenzoMinelli force-pushed the rm--signed-challenge-store branch from def29c6 to ec1c50e Compare October 8, 2026 21:57
@RenzoMinelli
RenzoMinelli changed the base branch from rm--cache-challenge-store to rm--api-two-factor October 8, 2026 21:57
RenzoMinelli added a commit that referenced this pull request Oct 9, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RenzoMinelli
RenzoMinelli force-pushed the rm--signed-challenge-store branch from ec1c50e to 8172dfb Compare October 9, 2026 13:17
RenzoMinelli and others added 4 commits October 9, 2026 17:30
- Accept `public_key_credential` as a JSON object or string.
- Respect `skip_session_storage` (`:params_auth`) in both WebAuthn
  strategies.
- Load the options controllers under an `ActionController::API` parent.

Moved from #152, which now only adds the cache challenge store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Non-navigational requests get `201`/`204`, or `422` with an `error`
message, instead of a redirect. Browsers still get redirects and flashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…llers

`respond_with_navigational` keeps redirects and flashes for browsers. Other
formats get `201` with the credential, `204`, or `422` with `errors`.
A credential that fails validation now counts as a failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two-factor strategy reads the session in `valid?`, so the override has
no effect for clients without a session until #154.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RenzoMinelli and others added 9 commits October 9, 2026 17:39
… clients

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a password sign-in needs a second factor and the request is not
navigational, respond `401` with a `two_factor_token`. The security key
options and two-factor endpoints accept it in place of the session.
Two-factor sign-in follows `skip_session_storage` (`:params_auth`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng sign-in

Browsers keep the redirect, so the failure app does not store the
two-factor page as the location to return to after sign-in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`write` now returns the challenge the options controllers render. The credential lookup moves to `ChallengeStores::CredentialChallenge` so other stores can share it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server signs each challenge with secret_key_base and checks the signature when the credential returns it in clientDataJSON. API clients need no session and no shared cache. A signed challenge can be reused until it expires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… stays at 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RenzoMinelli
RenzoMinelli force-pushed the rm--signed-challenge-store branch 2 times, most recently from 0811c70 to b3ed7ec Compare October 9, 2026 20:55
@RenzoMinelli
RenzoMinelli force-pushed the rm--api-two-factor branch 2 times, most recently from 16d3ee6 to e57864a Compare October 13, 2026 18:02
Base automatically changed from rm--api-two-factor to master October 14, 2026 21:05
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.

1 participant