Polish up Passkey interface

- Fix indentation
- Split awkward initializer into separate methods
- Rename credentials to passkeys
- Simplify authenticator registry loading
- Flatten nested errors under ActionPack::WebAuthn
- Set passkey current params only requests that use it
- Inline anemic methods
- Replace custom validation with ActiveModel::Validation
- Rename identity to holder in Passkey
- Rename credentials to passkeys in JS
- Extract framework library out of controllers
- Pass params hashes down to ActionPack
- Attempt to simplify public interface
- Push data decoding down to the classes representing the data
- Introduce has_passkeys
- Add CBOR bigint support
- Rename ActionPack::WebAuthn::Passkey to ActionPack::Passkey
- Add create_passkey_button helper
- Rename public-key to creation-options
- Add sign_in_with_passkey_button helper
- Dispatch events for the whole Passkey lifecycle
- Add ED25519 support
- Prevent crash on missing meta tag
- Validate resident key options
- Don't clobber existing ActionPack config options
- Validate cryptographic params
- Move CurrentWebAuthnRequest into ActionPack::Passkey
- Use ActiveModel::Attributes for Options objects
- Implement expiring challanges
- Add lifecycle events
- Extract param helpers into Request
- Add passkey_creation_options and passkey_request_options helpers
- Add create_passkey_challenge to make it easier to override the create method if needed
- Prefix all view helpers with passkey_
- Auto-include ActionPack::Passkey::Holder
- Make the passkey challange url configurable
- Add a reminder about Passkeys to the magic link email
This commit is contained in:
Stanko K.R.
2026-03-04 14:12:40 +01:00
parent f4fbe17b22
commit 017bcc9ce1
73 changed files with 2326 additions and 1103 deletions
@@ -1,44 +1,27 @@
class Sessions::PasskeysController < ApplicationController
include ActionPack::Passkey::Request
disallow_account_scope
require_unauthenticated_access
rate_limit to: 10, within: 3.minutes, only: :create, with: :rate_limit_exceeded
def create
credential = Identity::Credential.authenticate(
passkey: passkey_params,
challenge: session.delete(:webauthn_challenge)
)
if credential
authentication_succeeded(credential.identity)
else
authentication_failed
end
end
private
def passkey_params
params.expect(passkey: [ :id, :client_data_json, :authenticator_data, :signature ])
end
def authentication_succeeded(identity)
start_new_session_for identity
if credential = ActionPack::Passkey.authenticate(passkey_request_params)
start_new_session_for credential.holder
respond_to do |format|
format.html { redirect_to after_authentication_url }
format.json { render json: { session_token: session_token } }
end
end
def authentication_failed
alert_message = "That passkey didn't work. Try again."
else
respond_to do |format|
format.html { redirect_to new_session_path, alert: alert_message }
format.json { render json: { message: alert_message }, status: :unauthorized }
format.html { redirect_to new_session_path, alert: "That passkey didn't work. Try again." }
format.json { render json: { message: "That passkey didn't work. Try again." }, status: :unauthorized }
end
end
end
private
def rate_limit_exceeded
rate_limit_exceeded_message = "Try again later."