passkey

package
v0.27.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Index

Constants

View Source
const (
	EventRegistrationOptionsCreated   = "passkey:registration_options:created"
	EventRegistrationVerified         = "passkey:registration:verified"
	EventRegistrationFailed           = "passkey:registration:failed"
	EventAuthenticationOptionsCreated = "passkey:authentication_options:created"
	EventAuthenticationVerified       = "passkey:authentication:verified"
	EventAuthenticationFailed         = "passkey:authentication:failed"
	EventPasskeyUpdated               = "passkey:updated"
	EventPasskeyDeleted               = "passkey:deleted"
)

Event topic names for Passkey lifecycle events published to the EventBus.

View Source
const (
	ExtraKeyOrigin         = "origin"
	ExtraKeyAAGUID         = "aaguid"
	ExtraKeyDeviceType     = "deviceType"
	ExtraKeyTransports     = "transports"
	ExtraKeyChallengeToken = "challengeToken"
	ExtraKeyUserAgent      = "userAgent"
	ExtraKeyIPAddress      = "ipAddress"
)

Metadata keys for extra context passed in parameters or event payloads.

View Source
const (
	StoreKeyRPID        = "passkey:rp_id"
	StoreKeyRPOrigins   = "passkey:rp_origins"
	StoreKeyRPName      = "passkey:rp_name"
	StoreKeyActiveCount = "passkey:active_count"
)

Shared context storage keys for plugin.Context.

View Source
const AnonymousAAGUID = "00000000-0000-0000-0000-000000000000"

AnonymousAAGUID represents the zero/anonymous AAGUID indicating an unauthenticated or privacy-preserving model.

Variables

View Source
var (
	// ErrPasskeyNotFound is returned when no registered passkey credential matches the query.
	ErrPasskeyNotFound = errors.New("passkey: passkey not found")

	// ErrPasskeyAlreadyExists is returned when attempting to register a credential that is already bound to an account.
	ErrPasskeyAlreadyExists = errors.New("passkey: passkey credential already registered")

	// ErrChallengeNotFound is returned when a WebAuthn ceremony challenge token is missing or invalid.
	ErrChallengeNotFound = errors.New("passkey: challenge not found or expired")

	// ErrChallengeExpired is returned when a submitted WebAuthn challenge has passed its expiration time.
	ErrChallengeExpired = errors.New("passkey: challenge has expired")

	// ErrInvalidCeremonyType is returned when a challenge issued for registration is used for authentication or vice-versa.
	ErrInvalidCeremonyType = errors.New("passkey: invalid ceremony type for this challenge")

	// ErrUnauthorized is returned when an unauthenticated session attempts to register a passkey without authorization.
	ErrUnauthorized = errors.New("passkey: unauthorized to perform this operation")

	// ErrVerificationFailed is returned when WebAuthn cryptographic assertion/attestation verification fails.
	ErrVerificationFailed = errors.New("passkey: failed to verify webauthn ceremony response")

	// ErrCounterNotIncremented is returned when an authenticator signature counter does not increment, indicating potential cloning.
	ErrCounterNotIncremented = errors.New("passkey: signature counter did not increment (possible authenticator clone)")

	// ErrUserNotFound is returned when the target user record cannot be found.
	ErrUserNotFound = errors.New("passkey: user not found")

	// ErrSessionRequired is returned when passkey registration is attempted without an active session while requireSession is true.
	ErrSessionRequired = errors.New("passkey: passkey registration requires an authenticated session")

	// ErrResolveUserRequired is returned when requireSession is false but no resolveUser callback is configured.
	ErrResolveUserRequired = errors.New("passkey: resolveUser callback is required when requireSession is false and no session exists")

	// ErrInvalidResolvedUser is returned when a custom resolveUser callback returns a nil user entity.
	ErrInvalidResolvedUser = errors.New("passkey: resolved user is invalid")

	// ErrOriginMissing is returned when RP origin cannot be resolved from the HTTP request context.
	ErrOriginMissing = errors.New("passkey: origin is missing in request context")

	// ErrInvalidParameter is returned when a required input parameter is missing or malformed.
	ErrInvalidParameter = errors.New("passkey: invalid parameter provided")

	// ErrUnableToCreateSession is returned when creating a user session fails after successful WebAuthn authentication.
	ErrUnableToCreateSession = errors.New("passkey: unable to create user session")

	// ErrFailedToUpdatePasskey is returned when updating passkey metadata or counter fails.
	ErrFailedToUpdatePasskey = errors.New("passkey: failed to update passkey")
)

Sentinel errors for the Passkey plugin.

View Source
var CommonAuthenticatorNames = map[string]string{
	"ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4": "Google Password Manager",
	"fbfc3007-154e-4ecc-8c0b-6e020557d7bd": "Apple Passwords",
	"dd4ec289-e01d-41c9-bb89-70fa845d4bf2": "iCloud Keychain (Managed)",
	"08987058-cadc-4b81-b6e1-30de50dcbe96": "Windows Hello",
	"9ddd1817-af5a-4672-a2b9-3e3dd95000a9": "Windows Hello",
	"6028b017-b1d4-4c02-b4b3-afcdafc96bb2": "Windows Hello",
	"bada5566-a7aa-401f-bd96-45619a55120d": "1Password",
	"d548826e-79b4-db40-a3d8-11116f7e8349": "Bitwarden",
	"531126d6-e717-415c-9320-3d9aa6981239": "Dashlane",
	"b78a0a55-6ef8-d246-a042-ba0f6d55050c": "LastPass",
	"b84e4048-15dc-4dd0-8640-f4f60813c8af": "NordPass",
	"50726f74-6f6e-5061-7373-50726f746f6e": "Proton Pass",
	"0ea242b4-43c4-4a1b-8b17-dd6d0b6baec6": "Keeper",
	"53414d53-554e-4700-0000-000000000000": "Samsung Pass",
	"cb69481e-8ff7-4039-93ec-0a2729a1ef67": "YubiKey 5 Series",
	"ee882879-721c-4916-956b-f40570e30973": "YubiKey 5 NFC",
	"fa2b99dc-9e39-4257-8f92-4a30d23c4118": "YubiKey 5C NFC",
}

CommonAuthenticatorNames maps known Authenticator Attestation GUIDs (AAGUID) to human-friendly provider names.

Functions

func GetAuthenticatorName

func GetAuthenticatorName(aaguid *string) string

GetAuthenticatorName returns the descriptive name of an authenticator given its AAGUID, or "Passkey" as fallback.

Types

type AfterAuthenticationHook

type AfterAuthenticationHook func(ctx context.Context, passkey *entity.Passkey, user *entity.User, session *entity.Session) error

AfterAuthenticationHook is executed after a passkey assertion is verified and a new session created.

type AfterRegistrationHook

type AfterRegistrationHook func(ctx context.Context, passkey *entity.Passkey, user *entity.User) error

AfterRegistrationHook is executed after a new passkey credential has been successfully verified and saved.

type AuthenticationFailedPayload

type AuthenticationFailedPayload struct {
	UserID         *string        `json:"userId,omitempty"`
	ChallengeToken string         `json:"challengeToken"`
	Reason         string         `json:"reason"`
	Extra          map[string]any `json:"extra,omitempty"`
	Timestamp      time.Time      `json:"timestamp"`
}

AuthenticationFailedPayload is dispatched when an authentication assertion fails.

type AuthenticationOptionsCreatedPayload

type AuthenticationOptionsCreatedPayload struct {
	UserID         *string        `json:"userId,omitempty"`
	ChallengeToken string         `json:"challengeToken"`
	ExpiresAt      time.Time      `json:"expiresAt"`
	Extra          map[string]any `json:"extra,omitempty"`
	Timestamp      time.Time      `json:"timestamp"`
}

AuthenticationOptionsCreatedPayload is dispatched when an authentication assertion challenge is created.

type AuthenticationOptionsResult

type AuthenticationOptionsResult struct {
	Options        *protocol.CredentialAssertion `json:"options"`
	ChallengeToken string                        `json:"challengeToken"`
	ExpiresAt      time.Time                     `json:"expiresAt"`
}

AuthenticationOptionsResult contains assertion request options and the tracking challenge token.

type AuthenticationVerifiedPayload

type AuthenticationVerifiedPayload struct {
	Passkey   *entity.Passkey `json:"passkey"`
	User      *entity.User    `json:"user"`
	Session   *entity.Session `json:"session"`
	Extra     map[string]any  `json:"extra,omitempty"`
	Timestamp time.Time       `json:"timestamp"`
}

AuthenticationVerifiedPayload is dispatched when a passkey assertion is verified and session created.

type CeremonyType

type CeremonyType string

CeremonyType defines the type of WebAuthn ceremony.

const (
	CeremonyRegistration   CeremonyType = "registration"
	CeremonyAuthentication CeremonyType = "authentication"
)

type Config

type Config struct {
	RPDisplayName                string                               // Relying Party human-readable name (default: "GoModularAuth")
	RPID                         string                               // Relying Party domain identifier (default: "localhost")
	RPOrigins                    []string                             // Allowed origin URLs (e.g. "http://localhost:3000")
	ChallengeTimeout             time.Duration                        // Ephemeral challenge lifespan (default: 5 minutes)
	RequireSessionOnRegistration bool                                 // Enforce caller session during registration (default: true)
	UserVerification             protocol.UserVerificationRequirement // User verification requirement (default: "preferred")
	ResidentKey                  protocol.ResidentKeyRequirement      // Resident key / Discoverable credential preference (default: "preferred")
	Attestation                  protocol.ConveyancePreference        // Attestation conveyance preference (default: "none")
	AuthenticatorAttachment      *protocol.AuthenticatorAttachment    // Optional attachment ("platform" or "cross-platform")
	SessionDuration              time.Duration                        // Lifespan of created user sessions (default: 7 days)
	ResolveUser                  ResolveUserFunc                      // User resolution callback
	AfterRegistration            AfterRegistrationHook                // Post-registration hook
	AfterAuthentication          AfterAuthenticationHook              // Post-authentication hook
}

Config holds runtime configuration options for the Passkey plugin.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a Config populated with production-ready defaults.

type DeletePasskeyParams

type DeletePasskeyParams struct {
	ID           string `json:"id"`
	CallerUserID string `json:"callerUserId"`
	plugin.ExtraContainer
}

DeletePasskeyParams contains deletion parameters for an existing passkey.

type GenerateAuthenticationOptionsParams

type GenerateAuthenticationOptionsParams struct {
	UserID *string `json:"userId,omitempty"` // Optional. Omit for discoverable/resident key/autofill login.
	plugin.ExtraContainer
}

GenerateAuthenticationOptionsParams holds inputs for creating a WebAuthn assertion ceremony challenge.

type GenerateRegistrationOptionsParams

type GenerateRegistrationOptionsParams struct {
	UserID                  string                            `json:"userId,omitempty"`
	UserName                string                            `json:"userName,omitempty"`
	UserDisplayName         string                            `json:"userDisplayName,omitempty"`
	AuthenticatorAttachment *protocol.AuthenticatorAttachment `json:"authenticatorAttachment,omitempty"`
	Context                 *string                           `json:"context,omitempty"`
	CustomName              *string                           `json:"customName,omitempty"`
	plugin.ExtraContainer
}

GenerateRegistrationOptionsParams holds inputs for creating a WebAuthn credential registration ceremony challenge.

type ListPasskeysParams

type ListPasskeysParams struct {
	UserID string `json:"userId"`
	plugin.ExtraContainer
}

ListPasskeysParams filters passkeys for a specific user.

type Option

type Option func(*Config)

Option configures the Passkey plugin.

func WithAfterAuthentication

func WithAfterAuthentication(hook AfterAuthenticationHook) Option

WithAfterAuthentication sets the post-authentication lifecycle hook.

func WithAfterRegistration

func WithAfterRegistration(hook AfterRegistrationHook) Option

WithAfterRegistration sets the post-registration lifecycle hook.

func WithAttestation

func WithAttestation(att protocol.ConveyancePreference) Option

WithAttestation sets the attestation conveyance preference.

func WithAuthenticatorAttachment

func WithAuthenticatorAttachment(attachment protocol.AuthenticatorAttachment) Option

WithAuthenticatorAttachment restricts authenticators to platform or cross-platform devices.

func WithChallengeTimeout

func WithChallengeTimeout(d time.Duration) Option

WithChallengeTimeout sets the validity duration for ephemeral challenges.

func WithRPDisplayName

func WithRPDisplayName(name string) Option

WithRPDisplayName sets the Relying Party human-readable name.

func WithRPID

func WithRPID(rpID string) Option

WithRPID sets the Relying Party domain identifier (e.g. "auth.example.com" or "localhost").

func WithRPOrigins

func WithRPOrigins(origins ...string) Option

WithRPOrigins sets the list of allowed origin URLs.

func WithRequireSessionOnRegistration

func WithRequireSessionOnRegistration(require bool) Option

WithRequireSessionOnRegistration toggles whether registration requires an active authenticated caller.

func WithResidentKey

func WithResidentKey(rk protocol.ResidentKeyRequirement) Option

WithResidentKey sets the resident key (discoverable credential) preference.

func WithResolveUser

func WithResolveUser(fn ResolveUserFunc) Option

WithResolveUser sets the callback to resolve user identity during unauthenticated registration flows.

func WithSessionDuration

func WithSessionDuration(d time.Duration) Option

WithSessionDuration sets the validity duration for sessions issued upon successful authentication.

func WithUserVerification

func WithUserVerification(uv protocol.UserVerificationRequirement) Option

WithUserVerification sets the WebAuthn user verification preference.

type PasskeyChallenge

type PasskeyChallenge struct {
	Token       string       `json:"token"`
	Type        CeremonyType `json:"type"`
	Challenge   string       `json:"challenge"`
	UserID      *string      `json:"userId,omitempty"`
	UserName    *string      `json:"userName,omitempty"`
	DisplayName *string      `json:"displayName,omitempty"`
	Context     *string      `json:"context,omitempty"`
	SessionData string       `json:"sessionData"` // Serialized JSON of webauthn.SessionData
	ExpiresAt   time.Time    `json:"expiresAt"`
	CreatedAt   time.Time    `json:"createdAt"`
}

PasskeyChallenge represents the ephemeral state of an in-flight WebAuthn challenge.

type PasskeyDeletedPayload

type PasskeyDeletedPayload struct {
	PasskeyID string    `json:"passkeyId"`
	UserID    string    `json:"userId"`
	Timestamp time.Time `json:"timestamp"`
}

PasskeyDeletedPayload is dispatched when a passkey is deleted.

type PasskeyRegistrationUser

type PasskeyRegistrationUser struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	DisplayName string `json:"displayName"`
}

PasskeyRegistrationUser represents a resolved user identity when registering without an active session.

type PasskeyUpdatedPayload

type PasskeyUpdatedPayload struct {
	Passkey   *entity.Passkey `json:"passkey"`
	OldName   *string         `json:"oldName,omitempty"`
	NewName   string          `json:"newName"`
	Timestamp time.Time       `json:"timestamp"`
}

PasskeyUpdatedPayload is dispatched when a passkey's metadata/name is updated.

type Plugin

type Plugin struct {
	// contains filtered or unexported fields
}

Plugin implements the plugin.Plugin interface for FIDO2/WebAuthn Passkey authentication.

func New

func New(repo Repository, opts ...Option) *Plugin

New creates and returns a new Passkey plugin configured with the given repository and options.

func (*Plugin) Config

func (p *Plugin) Config() Config

Config returns a copy of the current configuration.

func (*Plugin) DeletePasskey

func (p *Plugin) DeletePasskey(ctx context.Context, params *DeletePasskeyParams) error

DeletePasskey removes a passkey after verifying caller ownership.

func (*Plugin) GenerateAuthenticationOptions

func (p *Plugin) GenerateAuthenticationOptions(ctx context.Context, params *GenerateAuthenticationOptionsParams) (*AuthenticationOptionsResult, error)

GenerateAuthenticationOptions begins a WebAuthn assertion ceremony for signing in.

func (*Plugin) GenerateRegistrationOptions

func (p *Plugin) GenerateRegistrationOptions(ctx context.Context, params *GenerateRegistrationOptionsParams) (*RegistrationOptionsResult, error)

GenerateRegistrationOptions begins a WebAuthn credential registration ceremony.

func (*Plugin) GetPasskey

func (p *Plugin) GetPasskey(ctx context.Context, id string) (*entity.Passkey, error)

GetPasskey retrieves a single passkey by its unique identifier.

func (*Plugin) ID

func (p *Plugin) ID() string

ID returns the unique identifier for the Passkey plugin.

func (*Plugin) Init

func (p *Plugin) Init(ctx *plugin.Context) error

Init initializes the plugin within the core engine and configures the WebAuthn cryptographic engine.

func (*Plugin) ListPasskeys

func (p *Plugin) ListPasskeys(ctx context.Context, params *ListPasskeysParams) ([]*entity.Passkey, error)

ListPasskeys retrieves all passkeys associated with a user.

func (*Plugin) ServeAuthenticationOptions added in v0.20.0

func (p *Plugin) ServeAuthenticationOptions(w http.ResponseWriter, r *http.Request)

ServeAuthenticationOptions is a net/http handler for generating WebAuthn passkey authentication options.

func (*Plugin) ServeRegistrationOptions added in v0.20.0

func (p *Plugin) ServeRegistrationOptions(w http.ResponseWriter, r *http.Request)

ServeRegistrationOptions is a net/http handler for generating WebAuthn passkey registration options.

func (*Plugin) ServeVerifyAuthentication added in v0.20.0

func (p *Plugin) ServeVerifyAuthentication(w http.ResponseWriter, r *http.Request)

ServeVerifyAuthentication is a net/http handler for verifying WebAuthn passkey authentication.

func (*Plugin) ServeVerifyRegistration added in v0.20.0

func (p *Plugin) ServeVerifyRegistration(w http.ResponseWriter, r *http.Request)

ServeVerifyRegistration is a net/http handler for verifying WebAuthn passkey registration.

func (*Plugin) UpdatePasskey

func (p *Plugin) UpdatePasskey(ctx context.Context, params *UpdatePasskeyParams) (*entity.Passkey, error)

UpdatePasskey modifies a passkey's friendly name after verifying ownership.

func (*Plugin) VerifyAuthentication

func (p *Plugin) VerifyAuthentication(ctx context.Context, params *VerifyAuthenticationParams) (*VerifyAuthenticationResult, error)

VerifyAuthentication verifies a WebAuthn assertion response and issues an authenticated session.

func (*Plugin) VerifyRegistration

func (p *Plugin) VerifyRegistration(ctx context.Context, params *VerifyRegistrationParams) (*entity.Passkey, error)

VerifyRegistration verifies the response from navigator.credentials.create() and registers the passkey.

type RegistrationFailedPayload

type RegistrationFailedPayload struct {
	UserID         *string        `json:"userId,omitempty"`
	ChallengeToken string         `json:"challengeToken"`
	Reason         string         `json:"reason"`
	Extra          map[string]any `json:"extra,omitempty"`
	Timestamp      time.Time      `json:"timestamp"`
}

RegistrationFailedPayload is dispatched when registration ceremony verification fails.

type RegistrationOptionsCreatedPayload

type RegistrationOptionsCreatedPayload struct {
	UserID         string         `json:"userId"`
	UserName       string         `json:"userName"`
	ChallengeToken string         `json:"challengeToken"`
	ExpiresAt      time.Time      `json:"expiresAt"`
	Extra          map[string]any `json:"extra,omitempty"`
	Timestamp      time.Time      `json:"timestamp"`
}

RegistrationOptionsCreatedPayload is dispatched when a registration challenge is generated.

type RegistrationOptionsResult

type RegistrationOptionsResult struct {
	Options        *protocol.CredentialCreation `json:"options"`
	ChallengeToken string                       `json:"challengeToken"`
	ExpiresAt      time.Time                    `json:"expiresAt"`
}

RegistrationOptionsResult contains creation options sent to the browser and the associated challenge token.

type RegistrationVerifiedPayload

type RegistrationVerifiedPayload struct {
	Passkey   *entity.Passkey `json:"passkey"`
	User      *entity.User    `json:"user"`
	Extra     map[string]any  `json:"extra,omitempty"`
	Timestamp time.Time       `json:"timestamp"`
}

RegistrationVerifiedPayload is dispatched when a new passkey credential is authenticated and persisted.

type Repository

type Repository interface {
	// CreatePasskey persists a new WebAuthn passkey credential record.
	//
	// Function:
	//   Called upon completing a WebAuthn registration ceremony to store public key credentials.
	//
	// Storage:
	//   Database (GORM / SQL) - Persistent storage for WebAuthn public keys.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - passkey: The Passkey entity containing credential ID, public key, counter, and transports.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   INSERT INTO passkeys (id, user_id, credential_id, public_key, counter, aaguid, name, created_at, updated_at)
	//   VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
	CreatePasskey(ctx context.Context, passkey *entity.Passkey) error

	// GetPasskeyByID retrieves a passkey credential by its primary key ID.
	//
	// Function:
	//   Used during passkey management (viewing or renaming passkeys).
	//
	// Storage:
	//   Database (GORM / SQL) - Passkey record lookup by ID.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - id: Unique passkey record identifier.
	//
	// Returns:
	//   - *entity.Passkey: Matching passkey entity if found.
	//   - error: ErrPasskeyNotFound if missing, or database error.
	//
	// Example SQL:
	//   SELECT id, user_id, credential_id, public_key, counter, aaguid, name, created_at, updated_at FROM passkeys WHERE id = $1 LIMIT 1;
	GetPasskeyByID(ctx context.Context, id string) (*entity.Passkey, error)

	// GetPasskeyByCredentialID retrieves a passkey credential by its raw WebAuthn credential ID.
	//
	// Function:
	//   Used during authentication ceremony verification to locate the matching public key.
	//
	// Storage:
	//   Database (GORM / SQL) - Query by unique credential ID string.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - credentialID: Base64URL encoded credential ID string.
	//
	// Returns:
	//   - *entity.Passkey: Matching passkey entity if found.
	//   - error: ErrPasskeyNotFound if missing, or database error.
	//
	// Example SQL:
	//   SELECT id, user_id, credential_id, public_key, counter, aaguid, name, created_at, updated_at FROM passkeys WHERE credential_id = $1 LIMIT 1;
	GetPasskeyByCredentialID(ctx context.Context, credentialID string) (*entity.Passkey, error)

	// ListPasskeysByUserID retrieves all registered passkey credentials belonging to a user.
	//
	// Function:
	//   Used during user security settings listing or passwordless sign-in user credential discovery.
	//
	// Storage:
	//   Database (GORM / SQL) - Query registered credentials by user ID.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - userID: Target user identifier.
	//
	// Returns:
	//   - []*entity.Passkey: Slice of registered passkey credentials.
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   SELECT id, user_id, credential_id, public_key, counter, aaguid, name, created_at, updated_at FROM passkeys WHERE user_id = $1;
	ListPasskeysByUserID(ctx context.Context, userID string) ([]*entity.Passkey, error)

	// UpdatePasskey updates mutable attributes of an existing passkey (e.g. friendly name).
	//
	// Function:
	//   Used when a user renames a registered passkey.
	//
	// Storage:
	//   Database (GORM / SQL) - Record update.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - passkey: Modified passkey entity.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   UPDATE passkeys SET name = $1, updated_at = $2 WHERE id = $3;
	UpdatePasskey(ctx context.Context, passkey *entity.Passkey) error

	// UpdatePasskeyCounter updates the signature counter of a passkey after successful assertion.
	//
	// Function:
	//   Called after validating an authentication ceremony response to prevent clone attacks.
	//
	// Storage:
	//   Database (GORM / SQL) - Signature counter update.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - id: Unique passkey record ID.
	//   - newCounter: Incrementally higher signature counter value.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   UPDATE passkeys SET counter = $1, updated_at = $2 WHERE id = $3;
	UpdatePasskeyCounter(ctx context.Context, id string, newCounter uint32) error

	// DeletePasskey permanently removes a single passkey credential record.
	//
	// Function:
	//   Called when a user revokes or deletes a passkey in security settings.
	//
	// Storage:
	//   Database (GORM / SQL) - Passkey credential deletion.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - id: Passkey record identifier.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   DELETE FROM passkeys WHERE id = $1;
	DeletePasskey(ctx context.Context, id string) error

	// DeletePasskeysByUserID purges all passkeys belonging to a user.
	//
	// Function:
	//   Called during user account deletion.
	//
	// Storage:
	//   Database (GORM / SQL) - Bulk passkey deletion.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - userID: Target user identifier.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   DELETE FROM passkeys WHERE user_id = $1;
	DeletePasskeysByUserID(ctx context.Context, userID string) error

	// SavePasskeyChallenge persists an ephemeral WebAuthn ceremony challenge record.
	//
	// Function:
	//   Called during generate-register-options and generate-authenticate-options endpoints.
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Short-lived in-flight challenge token with TTL.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - challenge: Ephemeral PasskeyChallenge state.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   INSERT INTO passkey_challenges (token, type, challenge, user_id, session_data, expires_at, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7);
	//
	// Example Cache (Redis):
	//   err := rdb.Set(ctx, "webauthn:challenge:" + challenge.Token, bytes, ttl).Err()
	SavePasskeyChallenge(ctx context.Context, challenge *PasskeyChallenge) error

	// GetPasskeyChallenge retrieves an active ceremony challenge record by challenge token string.
	//
	// Function:
	//   Used to inspect challenge state without consuming it.
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Ephemeral challenge lookup.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - token: Ephemeral challenge token identifier.
	//
	// Returns:
	//   - *PasskeyChallenge: Matching challenge state.
	//   - error: ErrChallengeNotFound if missing or expired.
	//
	// Example SQL:
	//   SELECT token, type, challenge, user_id, session_data, expires_at, created_at FROM passkey_challenges WHERE token = $1 LIMIT 1;
	//
	// Example Cache (Redis):
	//   val, err := rdb.Get(ctx, "webauthn:challenge:" + token).Bytes()
	GetPasskeyChallenge(ctx context.Context, token string) (*PasskeyChallenge, error)

	// ConsumePasskeyChallenge atomically retrieves and removes an in-flight ceremony challenge record.
	//
	// Function:
	//   Called during ceremony verification endpoints to ensure single-use replay protection.
	//
	// Storage:
	//   Cache (Redis GETDEL / Memory) - Atomic read-and-delete single-use challenge consumption.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - token: Ephemeral challenge token identifier.
	//
	// Returns:
	//   - *PasskeyChallenge: The consumed challenge state if valid.
	//   - error: ErrChallengeNotFound if missing, or ErrChallengeExpired if passed validity time.
	//
	// Example SQL:
	//   DELETE FROM passkey_challenges WHERE token = $1 AND expires_at > $2 RETURNING token, type, challenge, user_id, session_data, expires_at, created_at;
	//
	// Example Cache (Redis):
	//   val, err := rdb.GetDel(ctx, "webauthn:challenge:" + token).Bytes()
	ConsumePasskeyChallenge(ctx context.Context, token string) (*PasskeyChallenge, error)

	// DeletePasskeyChallenge removes a challenge record from storage by token.
	//
	// Function:
	//   Called during explicit cancellation or cleanup.
	//
	// Storage:
	//   Cache (Redis / In-Memory TTL) - Challenge token eviction.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - token: Challenge token string.
	//
	// Returns:
	//   - error: Nil on success, or database error.
	//
	// Example SQL:
	//   DELETE FROM passkey_challenges WHERE token = $1;
	//
	// Example Cache (Redis):
	//   err := rdb.Del(ctx, "webauthn:challenge:" + token).Err()
	DeletePasskeyChallenge(ctx context.Context, token string) error

	// GetUserByID retrieves user profile details by ID.
	//
	// Function:
	//   Used during passkey registration to populate WebAuthn User Entity details (name, display name).
	//
	// Storage:
	//   Database (GORM / SQL) - User primary key lookup.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - userID: Target user identifier.
	//
	// Returns:
	//   - *entity.User: Matching user entity if found.
	//   - error: ErrUserNotFound if missing, or database error.
	//
	// Example SQL:
	//   SELECT id, email, name, email_verified, created_at, updated_at FROM users WHERE id = $1 LIMIT 1;
	GetUserByID(ctx context.Context, userID string) (*entity.User, error)

	// GetUserByEmail retrieves user profile details by email address.
	//
	// Function:
	//   Used during passwordless authentication initiation when identifying user by email.
	//
	// Storage:
	//   Database (GORM / SQL) - Query user by email address.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - email: User primary email address.
	//
	// Returns:
	//   - *entity.User: Matching user entity if found.
	//   - error: ErrUserNotFound if missing, or database error.
	//
	// Example SQL:
	//   SELECT id, email, name, email_verified, created_at, updated_at FROM users WHERE email = $1 LIMIT 1;
	GetUserByEmail(ctx context.Context, email string) (*entity.User, error)

	// CreateSession initializes and persists a new authenticated user session.
	//
	// Function:
	//   Called after successful passkey authentication ceremony completion.
	//
	// Storage:
	//   Database (GORM / SQL) - Active session creation.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - session: DTO containing user ID, token, expiration, IP address, and user agent.
	//
	// Returns:
	//   - *entity.Session: Populated active session entity.
	//   - error: ErrUnableToCreateSession on failure.
	//
	// Example SQL:
	//   INSERT INTO sessions (id, user_id, token, expires_at, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6);
	CreateSession(ctx context.Context, session *dto.CreateSessionParams) (*entity.Session, error)
}

Repository defines the storage contract required by the Passkey authentication plugin. Implement this interface on your custom database adapter (e.g. PostgreSQL, MySQL, SQLite, MongoDB, GORM).

Implementation Example (GORM / database/sql):

type GormPasskeyRepository struct {
	db *gorm.DB
}

func (r *GormPasskeyRepository) GetPasskeyByCredentialID(ctx context.Context, credentialID string) (*entity.Passkey, error) {
	var pk entity.Passkey
	if err := r.db.WithContext(ctx).Where("credential_id = ?", credentialID).First(&pk).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, passkey.ErrPasskeyNotFound
		}
		return nil, err
	}
	return &pk, nil
}

Storage and Caching Recommendation (Ephemeral Challenge Caching):

In-flight WebAuthn challenges (`PasskeyChallenge`) are short-lived (e.g. 2-5 minutes) and single-use. Storing challenges in Redis or an in-memory key-value cache prevents unnecessary DB table writes:

type RedisPasskeyChallengeStore struct {
	redis *redis.Client
}

func (r *RedisPasskeyChallengeStore) SavePasskeyChallenge(ctx context.Context, ch *passkey.PasskeyChallenge) error {
	bytes, _ := json.Marshal(ch)
	ttl := time.Until(ch.ExpiresAt)
	return r.redis.Set(ctx, "webauthn:challenge:"+ch.Token, bytes, ttl).Err()
}

func (r *RedisPasskeyChallengeStore) ConsumePasskeyChallenge(ctx context.Context, token string) (*passkey.PasskeyChallenge, error) {
	key := "webauthn:challenge:" + token
	val, err := r.redis.GetDel(ctx, key).Bytes()
	if err != nil {
		return nil, passkey.ErrChallengeNotFound
	}
	var ch passkey.PasskeyChallenge
	_ = json.Unmarshal(val, &ch)
	return &ch, nil
}

type ResolveUserFunc

type ResolveUserFunc func(ctx context.Context, queryContext *string, extra map[string]any) (*PasskeyRegistrationUser, error)

ResolveUserFunc is invoked to resolve or provision a user identity during registration when requireSession is false.

type UpdatePasskeyParams

type UpdatePasskeyParams struct {
	ID           string `json:"id"`
	CallerUserID string `json:"callerUserId"`
	Name         string `json:"name"`
	plugin.ExtraContainer
}

UpdatePasskeyParams contains update parameters for an existing passkey.

type VerifyAuthenticationParams

type VerifyAuthenticationParams struct {
	ChallengeToken string                                `json:"challengeToken"`
	Origin         string                                `json:"origin,omitempty"`
	Response       *protocol.CredentialAssertionResponse `json:"response"`
	plugin.ExtraContainer
}

VerifyAuthenticationParams holds data returned from navigator.credentials.get() for verification.

type VerifyAuthenticationResult

type VerifyAuthenticationResult struct {
	User    *entity.User    `json:"user"`
	Session *entity.Session `json:"session"`
	Passkey *entity.Passkey `json:"passkey"`
}

VerifyAuthenticationResult contains authenticated identity, issued session, and the verified passkey.

type VerifyRegistrationParams

type VerifyRegistrationParams struct {
	ChallengeToken string                               `json:"challengeToken"`
	Origin         string                               `json:"origin,omitempty"`
	Response       *protocol.CredentialCreationResponse `json:"response"`
	Name           *string                              `json:"name,omitempty"`
	CallerUserID   *string                              `json:"callerUserId,omitempty"`
	plugin.ExtraContainer
}

VerifyRegistrationParams holds data returned from navigator.credentials.create() for verification.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL