Class: Unmagic::Passkeys::Credential

Inherits:
Object
  • Object
show all
Defined in:
app/models/unmagic/passkeys/credential.rb

Overview

Unmagic::Passkeys::Credential provides WebAuthn passkey registration and authentication backed by Active Record.

Passkeys are scoped to a polymorphic holder (typically a User or Identity) and store the credential ID, public key, sign count, and transport hints needed for the WebAuthn ceremonies.

Registration

Generate options for the browser’s navigator.credentials.create() call, then register the response:

options = Unmagic::Passkeys::Credential.registration_options(holder: current_user)
# Pass options to the browser

passkey = Unmagic::Passkeys::Credential.register(params[:passkey], holder: current_user)

Authentication

Generate options for the browser’s navigator.credentials.get() call, then authenticate the response:

options = Unmagic::Passkeys::Credential.authentication_options
# Pass options to the browser

passkey = Unmagic::Passkeys::Credential.authenticate(params[:passkey])

Holder integration

Call has_passkeys in your model to set up the association and configure ceremony options per-holder. See Unmagic::Passkeys::Holder for details.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.authenticate(passkey) ⇒ Object

Looks up a passkey by credential ID and verifies the assertion response from the browser. Returns the authenticated Passkey record, or nil if the credential is not found or verification fails.



76
77
78
# File 'app/models/unmagic/passkeys/credential.rb', line 76

def authenticate(passkey)
  find_by(credential_id: passkey[:id])&.authenticate(passkey)
end

.authentication_options(holder: nil, **options) ⇒ Object

Returns a RequestOptions object suitable for passing to the browser’s navigator.credentials.get() call. When a holder is provided, their existing credentials are included so the browser can offer them for selection. Merges global defaults, holder options, and any additional options overrides.



65
66
67
68
69
70
71
# File 'app/models/unmagic/passkeys/credential.rb', line 65

def authentication_options(holder: nil, **options)
  Unmagic::Passkeys::WebAuthn::PublicKeyCredential.request_options(
    **Rails.configuration.unmagic_passkeys.web_authn.default_request_options.to_h,
    **holder&.passkey_authentication_options.to_h,
    **options
  )
end

.register(passkey, **attributes) ⇒ Object

Verifies the attestation response from the browser and persists a new passkey record. The passkey hash should contain client_data_json, attestation_object, and transports as submitted by the registration form. The challenge is extracted from the authenticator’s clientDataJSON response and verified server-side. Any additional attributes (e.g. holder) are passed through to create!.

Raises Unmagic::Passkeys::WebAuthn::InvalidResponseError if the attestation is invalid.



55
56
57
58
59
# File 'app/models/unmagic/passkeys/credential.rb', line 55

def register(passkey, **attributes)
  credential = Unmagic::Passkeys::WebAuthn::PublicKeyCredential.register(passkey)

  create!(**credential.to_h, **attributes)
end

.registration_options(holder:, **options) ⇒ Object

Returns a CreationOptions object for the given holder, suitable for passing to the browser’s navigator.credentials.create() call. Merges global defaults from the Rails configuration, holder-specific options from holder.passkey_registration_options, and any additional options overrides.



40
41
42
43
44
45
46
# File 'app/models/unmagic/passkeys/credential.rb', line 40

def registration_options(holder:, **options)
  Unmagic::Passkeys::WebAuthn::PublicKeyCredential.creation_options(
    **Rails.configuration.unmagic_passkeys.web_authn.default_creation_options.to_h,
    **holder.passkey_registration_options.to_h,
    **options
  )
end

Instance Method Details

#authenticate(passkey) ⇒ Object

Verifies the assertion response against this passkey’s stored credential and updates the sign_count and backed_up attributes. Returns self on success, or nil if the response is invalid.



84
85
86
87
88
89
90
91
# File 'app/models/unmagic/passkeys/credential.rb', line 84

def authenticate(passkey)
  credential = to_public_key_credential
  credential.authenticate(passkey)
  update!(sign_count: credential.sign_count, backed_up: credential.backed_up)
  self
rescue Unmagic::Passkeys::WebAuthn::InvalidResponseError
  nil
end

#to_public_key_credentialObject

Returns an Unmagic::Passkeys::WebAuthn::PublicKeyCredential initialized from this record’s stored credential data.



95
96
97
98
99
100
101
102
# File 'app/models/unmagic/passkeys/credential.rb', line 95

def to_public_key_credential
  Unmagic::Passkeys::WebAuthn::PublicKeyCredential.new(
    id: credential_id,
    public_key: public_key,
    sign_count: sign_count,
    transports: transports
  )
end