passkey

package module
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: MIT Imports: 20 Imported by: 0

README

GoModularAuth - Passkey Plugin (gomodularauth-passkey)

Provides WebAuthn and Passkey registration and authentication as an optional server-side authentication mechanism for GoModularAuth.

Installation

go get github.com/BladiCreator/go-modular-auth/plugins/passkeys

Quick Start

package main

import (
	"log"
	"time"

	auth "github.com/BladiCreator/go-modular-auth"
	adaptermem "github.com/BladiCreator/go-modular-auth/adapters/memory"
	passkey "github.com/BladiCreator/go-modular-auth/plugins/passkeys"
	"github.com/BladiCreator/go-modular-auth/plugins/passkeys/plugins"
)

func main() {
	passkeyMod := passkey.New(
		passkey.WithRP("localhost", "My App", []string{"http://localhost:3000"}),
		passkey.WithTimeout(60 * time.Second),
	)

	authEngine, err := auth.New(auth.Config{
		Adapter: adaptermem.NewAdapter(),
		Plugins: []auth.PluginModule{passkeyMod},
	})
	if err != nil {
		log.Fatalf("failed to initialize auth: %v", err)
	}

	// Retrieve typed capability
	pk, err := auth.Get[plugins.Passkeys](authEngine)
	if err != nil {
		log.Fatalf("failed to retrieve passkey capability: %v", err)
	}

	_ = pk
}

Detailed guide: doc/plugins/passkeys.md.

Documentation

Overview

Package passkey implements WebAuthn / FIDO2 biometric and security key registration and passwordless authentication.

Index

Constants

View Source
const (
	// EventPasskeyRegistered is emitted when a new passkey credential is successfully registered.
	EventPasskeyRegistered auth.EventName = "passkey.registered"

	// EventPasskeyUsed is emitted when a user successfully authenticates using a passkey.
	EventPasskeyUsed auth.EventName = "passkey.used"

	// EventPasskeyRevoked is emitted when a passkey credential is deleted or revoked.
	EventPasskeyRevoked auth.EventName = "passkey.revoked"
)
View Source
const DefaultTimeout = 60 * time.Second

DefaultTimeout is the default duration for WebAuthn ceremony expiration.

View Source
const (
	ModelPasskeys = "passkeys"
)
View Source
const PluginID = "passkey"

PluginID is the unique identifier for this plugin module in GoModularAuth.

View Source
const ProviderPasskey = "passkey"

ProviderPasskey is the provider identifier used when recording linked passkey accounts in Core.

Variables

View Source
var (
	// ErrRPIDRequired indicates that relying party ID was not provided.
	ErrRPIDRequired = errors.New("passkey: relying party ID is required")

	// ErrRPDisplayNameRequired indicates that relying party display name was not provided.
	ErrRPDisplayNameRequired = errors.New("passkey: relying party display name is required")

	// ErrRPOriginsRequired indicates that at least one relying party origin is required.
	ErrRPOriginsRequired = errors.New("passkey: at least one relying party origin is required")

	// ErrInvalidRPID indicates that relying party ID contains an invalid scheme, port, or path.
	ErrInvalidRPID = errors.New("passkey: relying party ID must not contain schemes, ports, or paths")

	// ErrInvalidRPOrigin indicates that an origin URL is invalid or malformed.
	ErrInvalidRPOrigin = errors.New("passkey: relying party origin must be a valid URL with scheme")

	// ErrUserIDRequired indicates that a user ID was not supplied when required.
	ErrUserIDRequired = errors.New("passkey: user ID is required")

	// ErrCredentialIDRequired indicates that a credential ID was not provided.
	ErrCredentialIDRequired = errors.New("passkey: credential ID is required")

	// ErrSessionIDRequired indicates that a ceremony session ID was not provided.
	ErrSessionIDRequired = errors.New("passkey: ceremony session ID is required")

	// ErrChallengeNotFound indicates that the ceremony challenge does not exist.
	ErrChallengeNotFound = errors.New("passkey: ceremony challenge not found")

	// ErrChallengeExpired indicates that the ceremony challenge has expired.
	ErrChallengeExpired = errors.New("passkey: ceremony challenge has expired")

	// ErrChallengeAlreadyUsed indicates that the ceremony challenge was already consumed (replay prevention).
	ErrChallengeAlreadyUsed = errors.New("passkey: ceremony challenge has already been consumed")

	// ErrChallengeTypeMismatch indicates that the ceremony challenge type does not match the attempted operation.
	ErrChallengeTypeMismatch = errors.New("passkey: ceremony challenge type mismatch")

	// ErrInvalidClientResponse indicates that the browser client response was missing or malformed.
	ErrInvalidClientResponse = errors.New("passkey: invalid webauthn client response data")

	// ErrRegistrationFailed indicates that WebAuthn registration verification failed.
	ErrRegistrationFailed = errors.New("passkey: credential registration verification failed")

	// ErrAuthenticationFailed indicates that WebAuthn authentication verification failed.
	ErrAuthenticationFailed = errors.New("passkey: credential authentication verification failed")

	// ErrCredentialNotFound indicates that the requested passkey credential does not exist.
	ErrCredentialNotFound = errors.New("passkey: credential not found")

	// ErrCredentialAlreadyExists indicates that the credential ID is already registered.
	ErrCredentialAlreadyExists = errors.New("passkey: credential already registered")

	// ErrSignCounterInvalid indicates that the authenticator sign counter did not increment properly.
	ErrSignCounterInvalid = errors.New("passkey: sign counter invalid, potential replay or cloned authenticator")

	// ErrAuthenticatorCloned indicates that an authenticator clone was flagged during assertion verification.
	ErrAuthenticatorCloned = errors.New("passkey: authenticator clone detected")

	// ErrUserNotFound indicates that the user associated with the credential was not found in the core store.
	ErrUserNotFound = errors.New("passkey: user not found")

	// ErrUnauthorized indicates that the caller does not have permission to manage the credential.
	ErrUnauthorized = errors.New("passkey: unauthorized access to credential")
)

Functions

func RegisterSchema

func RegisterSchema(s *schema.Schema) error

RegisterSchema registers the passkeys model into the compiled schema.

Types

type AuthenticationOptions

type AuthenticationOptions struct {
	// SessionID is the opaque challenge session identifier used to finish authentication.
	SessionID string `json:"session_id"`

	// Options contains the standard WebAuthn PublicKeyCredentialRequestOptions.
	Options *protocol.CredentialAssertion `json:"options"`

	// ExpiresAt specifies when the challenge expires.
	ExpiresAt time.Time `json:"expires_at"`
}

AuthenticationOptions represents the authentication challenge options for the client.

type BeginAuthenticationInput

type BeginAuthenticationInput struct {
	// UserID is optional. If provided, server restricts allowed credentials to this user.
	// If empty, discoverable (usernameless / resident key) authentication is initiated.
	UserID string

	// UserVerification overrides the default user verification requirement.
	UserVerification protocol.UserVerificationRequirement
}

BeginAuthenticationInput contains options for starting an authentication ceremony.

type BeginRegistrationInput

type BeginRegistrationInput struct {
	// UserID identifies the user registering a passkey. Required.
	UserID string

	// FriendlyName is an optional human label for the device (e.g. "Pixel 8 Pro").
	FriendlyName string

	// UserVerification overrides the default user verification requirement.
	UserVerification protocol.UserVerificationRequirement

	// ResidentKey overrides the default resident key requirement.
	ResidentKey protocol.ResidentKeyRequirement

	// AuthenticatorAttachment optionally restricts attachment (platform or cross-platform).
	AuthenticatorAttachment protocol.AuthenticatorAttachment
}

BeginRegistrationInput contains options for starting a credential registration ceremony.

type Capability

type Capability = Passkeys

Capability is an alias for Passkeys for idiomatic capability retrieval.

type CeremonyError

type CeremonyError struct {
	Op         string
	Reason     string
	Underlying error
}

CeremonyError provides rich contextual error details around WebAuthn failures.

func (*CeremonyError) Error

func (e *CeremonyError) Error() string

Error returns the string representation of CeremonyError.

func (*CeremonyError) Unwrap

func (e *CeremonyError) Unwrap() error

Unwrap returns the underlying error.

type Config

type Config struct {
	// RPDisplayName is the human-readable Relying Party name (e.g. "Acme Corp"). Required.
	RPDisplayName string

	// RPID is the Relying Party server identifier (e.g. "acme.com" or "localhost"). Required.
	RPID string

	// RPOrigins lists the allowed origin URLs (e.g. "https://acme.com", "http://localhost:3000"). Required.
	RPOrigins []string

	// UserVerification specifies the default user verification requirement (required, preferred, discouraged).
	// Default: preferred.
	UserVerification protocol.UserVerificationRequirement

	// ResidentKey specifies the default resident key / discoverable credential requirement.
	// Default: preferred.
	ResidentKey protocol.ResidentKeyRequirement

	// Timeout defines the maximum duration allowed to complete a registration or login ceremony.
	// Default: 60 seconds.
	Timeout time.Duration

	// EnforceSignCounter ensures that authenticator sign counter regressions trigger clone detection errors.
	// Default: true.
	EnforceSignCounter bool

	// ChallengeStore is the storage adapter for transient ceremony challenges.
	// Defaults to in-memory challenge store if nil.
	ChallengeStore challenge.Store

	// Hooks provides extension hooks for ceremony workflows.
	Hooks *Hooks

	// Debug enables verbose debug logging in the WebAuthn engine.
	Debug bool
}

Config holds the configuration settings for the Passkey plugin.

type DeleteCredentialInput

type DeleteCredentialInput struct {
	// UserID verifies ownership. Required.
	UserID string

	// CredentialID is the binary ID of the credential to delete. Required.
	CredentialID []byte
}

DeleteCredentialInput holds parameters for deleting a passkey.

type FinishAuthenticationInput

type FinishAuthenticationInput struct {
	// SessionID is the ceremony session ID returned by BeginAuthentication. Required.
	SessionID string

	// Response is the raw JSON response payload from navigator.credentials.get().
	Response []byte

	// Body allows streaming the client JSON response directly from a reader.
	Body io.Reader

	// ParsedResponse allows supplying pre-parsed credential assertion data.
	ParsedResponse *protocol.ParsedCredentialAssertionData

	// Session holds optional IP and User-Agent metadata for the issued core session.
	Session auth.SessionOptions
}

FinishAuthenticationInput contains client response data for completing authentication.

type FinishRegistrationInput

type FinishRegistrationInput struct {
	// SessionID is the ceremony session ID returned by BeginRegistration. Required.
	SessionID string

	// Response is the raw JSON response payload from navigator.credentials.create().
	Response []byte

	// Body allows streaming the client JSON response directly from a reader.
	Body io.Reader

	// ParsedResponse allows supplying pre-parsed credential creation data.
	ParsedResponse *protocol.ParsedCredentialCreationData

	// FriendlyName optionally overrides the label provided during BeginRegistration.
	FriendlyName string
}

FinishRegistrationInput contains client response data for completing registration.

type Hooks

type Hooks struct {
	BeforeBeginRegistration  func(ctx context.Context, in *BeginRegistrationInput) error
	BeforeFinishRegistration func(ctx context.Context, in *FinishRegistrationInput) error
	AfterRegistration        func(ctx context.Context, cred *Passkey) error

	BeforeBeginAuthentication  func(ctx context.Context, in *BeginAuthenticationInput) error
	BeforeFinishAuthentication func(ctx context.Context, in *FinishAuthenticationInput) error
	AfterAuthentication        func(ctx context.Context, res *auth.AuthResult) error
}

Hooks provides lifecycle intervention points for passkey operations.

func NewHooks

func NewHooks() *Hooks

NewHooks creates an initialized Hooks structure.

type Module

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

Module implements auth.PluginModule for WebAuthn passkey authentication using plugin-sdk.

func New

func New(opts ...Option) *Module

New constructs an installable Passkey plugin module.

func (*Module) ID

func (m *Module) ID() string

ID returns the module's unique identifier.

func (*Module) Init

func (m *Module) Init(h *auth.Host) error

Init initializes the plugin module on the host and registers its capability.

func (*Module) RegisterSchema

func (m *Module) RegisterSchema(s *schema.Schema) error

RegisterSchema registers the passkeys model into the compiled schema.

type Option

type Option func(*Config)

Option applies a configuration option.

func WithChallengeStore

func WithChallengeStore(cs challenge.Store) Option

WithChallengeStore sets the transient ceremony challenge store.

func WithDebug

func WithDebug(debug bool) Option

WithDebug enables or disables WebAuthn debug output.

func WithEnforceSignCounter

func WithEnforceSignCounter(enforce bool) Option

WithEnforceSignCounter toggles sign counter validation and clone rejection.

func WithHooks

func WithHooks(h *Hooks) Option

WithHooks sets workflow lifecycle hooks.

func WithRP

func WithRP(id, displayName string, origins []string) Option

WithRP sets the Relying Party identification and allowed origins.

func WithResidentKey

func WithResidentKey(rk protocol.ResidentKeyRequirement) Option

WithResidentKey configures the resident key / discoverable credential policy.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the ceremony timeout.

func WithUserVerification

func WithUserVerification(uv protocol.UserVerificationRequirement) Option

WithUserVerification configures the user verification policy.

type Passkey

type Passkey struct {
	ID              string                            `json:"id"`
	CredentialID    []byte                            `json:"credential_id"`
	UserID          string                            `json:"user_id"`
	FriendlyName    string                            `json:"friendly_name"`
	PublicKey       []byte                            `json:"public_key"`
	AttestationType string                            `json:"attestation_type"`
	AAGUID          []byte                            `json:"aaguid"`
	SignCount       uint32                            `json:"sign_count"`
	CloneWarning    bool                              `json:"clone_warning"`
	Attachment      protocol.AuthenticatorAttachment  `json:"attachment"`
	Transports      []protocol.AuthenticatorTransport `json:"transports"`
	UserPresent     bool                              `json:"user_present"`
	UserVerified    bool                              `json:"user_verified"`
	BackupEligible  bool                              `json:"backup_eligible"`
	BackupState     bool                              `json:"backup_state"`
	CreatedAt       time.Time                         `json:"created_at"`
	UpdatedAt       time.Time                         `json:"updated_at"`
	LastUsedAt      *time.Time                        `json:"last_used_at,omitempty"`
	RevokedAt       *time.Time                        `json:"revoked_at,omitempty"`
}

Passkey represents a WebAuthn / FIDO2 credential registered by a user.

func FromWebAuthn

func FromWebAuthn(userID, friendlyName string, cred *webauthn.Credential, now time.Time) *Passkey

FromWebAuthn constructs a domain Passkey from a validated webauthn.Credential.

func (*Passkey) Active

func (c *Passkey) Active() bool

Active reports whether the passkey has not been revoked.

func (*Passkey) ToWebAuthn

func (c *Passkey) ToWebAuthn() webauthn.Credential

ToWebAuthn converts a Passkey into a webauthn.Credential instance.

type PasskeyRegisteredPayload

type PasskeyRegisteredPayload struct {
	UserID       string    `json:"user_id"`
	CredentialID string    `json:"credential_id"` // base64url encoded
	FriendlyName string    `json:"friendly_name"`
	AAGUID       string    `json:"aaguid,omitempty"`
	RegisteredAt time.Time `json:"registered_at"`
}

PasskeyRegisteredPayload conveys non-sensitive event metadata when a passkey is registered. Cryptographic keys and raw secrets are strictly excluded.

type PasskeyRevokedPayload

type PasskeyRevokedPayload struct {
	UserID       string    `json:"user_id"`
	CredentialID string    `json:"credential_id"` // base64url encoded
	RevokedAt    time.Time `json:"revoked_at"`
}

PasskeyRevokedPayload conveys non-sensitive event metadata when a passkey is revoked.

type PasskeyUsedPayload

type PasskeyUsedPayload struct {
	UserID       string    `json:"user_id"`
	CredentialID string    `json:"credential_id"` // base64url encoded
	SessionID    string    `json:"session_id"`
	UsedAt       time.Time `json:"used_at"`
}

PasskeyUsedPayload conveys non-sensitive event metadata when a passkey is used for authentication.

type Passkeys

type Passkeys interface {
	// BeginRegistration generates the public key options required by navigator.credentials.create().
	BeginRegistration(ctx context.Context, in BeginRegistrationInput) (*RegistrationOptions, error)

	// FinishRegistration validates the client response, persists the credential, and links the Core account.
	FinishRegistration(ctx context.Context, in FinishRegistrationInput) (*Passkey, error)

	// BeginAuthentication generates the options required by navigator.credentials.get().
	// Supports both user-specified and discoverable (usernameless / resident key) flows.
	BeginAuthentication(ctx context.Context, in BeginAuthenticationInput) (*AuthenticationOptions, error)

	// FinishAuthentication verifies the client assertion, checks sign counter replay protection,
	// and issues an authenticated session via GoModularAuth Core.
	FinishAuthentication(ctx context.Context, in FinishAuthenticationInput) (*auth.AuthResult, error)

	// ListCredentials returns all active passkeys registered to the specified user.
	ListCredentials(ctx context.Context, userID string) ([]*Passkey, error)

	// GetCredential returns a single passkey by credential ID with ownership verification.
	GetCredential(ctx context.Context, userID string, credentialID []byte) (*Passkey, error)

	// UpdateCredential updates metadata (e.g. friendly name) for an existing passkey.
	UpdateCredential(ctx context.Context, in UpdateCredentialInput) (*Passkey, error)

	// DeleteCredential revokes/removes the passkey and cleans up its associated Core account.
	DeleteCredential(ctx context.Context, in DeleteCredentialInput) error
}

Passkeys is the strongly typed public capability exposed by this plugin. Applications consume it via auth.Get[plugins.Passkeys](a) or auth.Get[passkey.Passkeys](a).

type RegistrationOptions

type RegistrationOptions struct {
	// SessionID is the opaque challenge session identifier used to finish registration.
	SessionID string `json:"session_id"`

	// Options contains the standard WebAuthn PublicKeyCredentialCreationOptions.
	Options *protocol.CredentialCreation `json:"options"`

	// ExpiresAt specifies when the challenge expires.
	ExpiresAt time.Time `json:"expires_at"`
}

RegistrationOptions represents the registration challenge and public key options for the client.

type UpdateCredentialInput

type UpdateCredentialInput struct {
	// UserID verifies ownership. Required.
	UserID string

	// CredentialID is the binary ID of the credential to update. Required.
	CredentialID []byte

	// FriendlyName is the new label for the passkey.
	FriendlyName string
}

UpdateCredentialInput holds parameters for updating passkey metadata.

Directories

Path Synopsis
Package plugins exposes strongly-typed capability aliases for GoModularAuth plugins.
Package plugins exposes strongly-typed capability aliases for GoModularAuth plugins.

Jump to

Keyboard shortcuts

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