Skip to content

Passkeys#2623

Draft
monorkin wants to merge 33 commits intomainfrom
passkeys
Draft

Passkeys#2623
monorkin wants to merge 33 commits intomainfrom
passkeys

Conversation

@monorkin
Copy link
Contributor

No description provided.

Copilot AI review requested due to automatic review settings February 27, 2026 10:27
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements passkey authentication (WebAuthn) for the application, allowing users to sign in using biometric authentication, security keys, or platform authenticators instead of traditional passwords or email codes.

Changes:

  • Adds a complete WebAuthn library implementation in lib/action_pack/web_authn/ including CBOR decoding, COSE key handling, and authenticator response validation
  • Implements Identity::Credential model for storing and managing user credentials with passkey registration and authentication methods
  • Adds UI for managing passkeys including registration, editing, and deletion of credentials

Reviewed changes

Copilot reviewed 51 out of 73 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/action_pack/web_authn/*.rb Core WebAuthn library implementing CBOR decoding, COSE key parsing, and authenticator response validation
app/models/identity/credential.rb Model for managing WebAuthn credentials with registration and authentication logic
app/models/identity/credential/authenticator.rb Registry for mapping AAGUIDs to authenticator metadata (icons, names)
app/controllers/users/credentials_controller.rb Controller for credential management (CRUD operations)
app/controllers/sessions/passkeys_controller.rb Controller for passkey-based authentication
app/javascript/controllers/*.js Stimulus controllers for credential registration and conditional mediation
app/views/users/credentials/*.html.erb Views for credential management UI
db/migrate/*.rb Migrations for identity_credentials table
config/passkey_aaguids.yml Configuration mapping AAGUIDs to authenticator details
test/lib/action_pack/web_authn/*.rb Comprehensive test coverage for WebAuthn library
test/controllers/sessions/passkeys_controller_test.rb Integration tests for passkey authentication

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

#
# # Look up the credential by ID
# credential = user.credentials.find_by!(
# credentail_id: params[:id]
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

Typo in comment: "credentail_id" should be "credential_id".

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +74
class Identity::Credential < ApplicationRecord
belongs_to :identity

serialize :transports, coder: JSON, type: Array, default: []

class << self
def creation_options(identity:, display_name:)
ActionPack::WebAuthn::PublicKeyCredential::CreationOptions.new(
id: identity.id,
name: identity.email_address,
display_name: display_name,
resident_key: :required,
exclude_credentials: identity.credentials.map(&:to_public_key_credential)
)
end

def request_options(credentials: [])
ActionPack::WebAuthn::PublicKeyCredential::RequestOptions.new(credentials: credentials.map(&:to_public_key_credential))
end

def register(passkey:, challenge:, origin: ActionPack::WebAuthn::Current.origin, **attributes)
public_key_credential = ActionPack::WebAuthn::PublicKeyCredential.create(
client_data_json: passkey[:client_data_json],
attestation_object: Base64.urlsafe_decode64(passkey[:attestation_object]),
challenge: challenge,
origin: origin,
transports: Array(passkey[:transports])
)

create!(
**attributes,
name: attributes.fetch(:name, Authenticator.find_by_aaguid(public_key_credential.aaguid)&.name),
credential_id: public_key_credential.id,
public_key: public_key_credential.public_key.to_der,
sign_count: public_key_credential.sign_count,
aaguid: public_key_credential.aaguid,
backed_up: public_key_credential.backed_up,
transports: public_key_credential.transports
)
end

def authenticate(passkey:, challenge:, origin: ActionPack::WebAuthn::Current.origin)
find_by(credential_id: passkey[:id])&.authenticate(passkey: passkey, challenge: challenge, origin: origin)
end
end

def authenticate(passkey:, challenge:, origin: ActionPack::WebAuthn::Current.origin)
pkc = to_public_key_credential
pkc.authenticate(
client_data_json: passkey[:client_data_json],
authenticator_data: Base64.urlsafe_decode64(passkey[:authenticator_data]),
signature: Base64.urlsafe_decode64(passkey[:signature]),
challenge: challenge,
origin: origin
)
update!(sign_count: pkc.sign_count, backed_up: pkc.backed_up)
self
rescue ActionPack::WebAuthn::Authenticator::Response::InvalidResponseError
nil
end

def authenticator
Authenticator.find_by_aaguid(aaguid)
end

def to_public_key_credential
ActionPack::WebAuthn::PublicKeyCredential.new(
id: credential_id,
public_key: OpenSSL::PKey.read(public_key),
sign_count: sign_count,
transports: transports
)
end
end
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

Missing model tests for Identity::Credential. The codebase has comprehensive test coverage for other models (as seen in test/models/), but there are no tests for the newly added Identity::Credential model. Consider adding tests to cover:

  • Credential creation and registration
  • Credential authentication
  • The authenticator lookup functionality
  • Public key credential conversion
  • Edge cases and error handling

Copilot uses AI. Check for mistakes.
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.

3 participants