passkey

package
v0.0.11 Latest Latest
Warning

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

Go to latest
Published: Jan 3, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

README

Passkey Plugin

PRODUCTION READY - Full WebAuthn/FIDO2 Implementation

The passkey plugin provides production-ready WebAuthn/FIDO2 passwordless authentication with cryptographic verification, supporting both standalone authentication and MFA integration.

Status

Production Ready - This plugin now includes:

  • ✅ Complete WebAuthn cryptographic implementation
  • ✅ Challenge generation with secure randomness
  • ✅ Attestation verification during registration
  • ✅ Signature verification during authentication
  • ✅ Sign count tracking for replay attack prevention
  • ✅ Resident key / discoverable credential support
  • ✅ Passkey naming and management
  • ✅ App and organization scoping
  • ✅ MFA integration support
  • ✅ Comprehensive test coverage

Features

Core WebAuthn Support
  • Platform Authenticators: Touch ID, Windows Hello, Face ID
  • Cross-Platform Authenticators: YubiKey, Google Titan, Feitian
  • Discoverable Credentials: Usernameless authentication
  • User Verification: Biometric or PIN requirements
  • Attestation Formats: Packed, FIDO U2F, TPM, Android SafetyNet
Security
  • Cryptographic challenge generation (32+ bytes)
  • Public key storage and signature verification
  • Sign count tracking for cloned authenticator detection
  • Challenge timeout (5 minutes default)
  • Replay attack prevention
  • App and organization scoping
Device Management
  • User-friendly passkey naming
  • Last used timestamp tracking
  • Authenticator type identification (platform vs cross-platform)
  • AAGUID for hardware key identification
  • Multiple passkeys per user

Installation

Add the passkey plugin to your AuthSome setup:

import (
    "github.com/xraph/authsome"
    "github.com/xraph/authsome/plugins/passkey"
)

func main() {
    // Create AuthSome instance
    auth := authsome.New(
        db,
        authsome.WithPlugins(
            passkey.NewPlugin(
                passkey.WithRPID("example.com"),
                passkey.WithRPName("My App"),
                passkey.WithTimeout(300000), // 5 minutes
                passkey.WithUserVerification("preferred"),
            ),
        ),
    )
}

Configuration

YAML Configuration
auth:
  passkey:
    rpid: "example.com"              # Relying Party ID (your domain)
    rpname: "My Application"         # Display name
    rporigins:                       # Allowed origins
      - "https://example.com"
      - "https://app.example.com"
    timeout: 300000                  # Challenge timeout (milliseconds)
    userverification: "preferred"    # required, preferred, or discouraged
    attestationtype: "none"          # none, indirect, or direct
    requireresidentkey: false        # Require resident keys
    authenticatorattachment: ""      # platform, cross-platform, or empty
    challengestorage: "memory"       # memory or redis
Programmatic Configuration
plugin := passkey.NewPlugin(
    passkey.WithRPID("example.com"),
    passkey.WithRPName("My App"),
    passkey.WithTimeout(300000),
    passkey.WithUserVerification("required"),
    passkey.WithAttestationType("direct"),
)

Usage

Standalone Passwordless Authentication
Registration Flow
// 1. Begin registration
POST /auth/passkey/register/begin
{
  "userId": "cjld2cjxh0000qzrmn831i7rn",
  "name": "MacBook Pro Touch ID",
  "authenticatorType": "platform",  // optional: "platform" or "cross-platform"
  "requireResidentKey": false,
  "userVerification": "preferred"   // optional: "required", "preferred", "discouraged"
}

// Response:
{
  "options": { /* WebAuthn PublicKeyCredentialCreationOptions */ },
  "challenge": "base64url_encoded_challenge",
  "userId": "cjld2cjxh0000qzrmn831i7rn",
  "timeout": 300000
}

// 2. Client calls navigator.credentials.create() with options
// 3. Finish registration with credential response

POST /auth/passkey/register/finish
{
  "userId": "cjld2cjxh0000qzrmn831i7rn",
  "name": "MacBook Pro Touch ID",
  "response": { /* WebAuthn PublicKeyCredential */ }
}

// Response:
{
  "passkeyId": "cjld2cjxh0001qzrmn831i7rn",
  "name": "MacBook Pro Touch ID",
  "status": "registered",
  "createdAt": "2025-01-15T10:30:00Z",
  "credentialId": "base64url_credential_id"
}
Authentication Flow
// 1. Begin authentication
POST /auth/passkey/login/begin
{
  "userId": "cjld2cjxh0000qzrmn831i7rn",  // optional for discoverable credentials
  "userVerification": "preferred"
}

// Response:
{
  "options": { /* WebAuthn PublicKeyCredentialRequestOptions */ },
  "challenge": "base64url_encoded_challenge",
  "timeout": 300000
}

// 2. Client calls navigator.credentials.get() with options
// 3. Finish authentication with assertion

POST /auth/passkey/login/finish
{
  "response": { /* WebAuthn PublicKeyCredential assertion */ },
  "remember": true
}

// Response:
{
  "user": { /* user object */ },
  "session": { /* session object */ },
  "token": "session_token",
  "passkeyUsed": "cjld2cjxh0001qzrmn831i7rn"
}
Discoverable Credentials (Usernameless)
// Begin login without userID
POST /auth/passkey/login/begin
{
  // No userId - supports autofill/conditional UI
}

// Client side with conditional UI
const publicKeyCredential = await navigator.credentials.get({
  publicKey: options,
  mediation: 'conditional'  // Enables autofill
});
Passkey Management
// List user's passkeys
GET /auth/passkey/list?userId=cjld2cjxh0000qzrmn831i7rn

// Response:
{
  "passkeys": [
    {
      "id": "cjld2cjxh0001qzrmn831i7rn",
      "name": "MacBook Pro Touch ID",
      "credentialId": "base64url_id",
      "aaguid": "base64url_aaguid",
      "authenticatorType": "platform",
      "createdAt": "2025-01-15T10:30:00Z",
      "lastUsedAt": "2025-01-20T14:22:00Z",
      "signCount": 42,
      "isResidentKey": true
    }
  ],
  "count": 1
}

// Update passkey name
PUT /auth/passkey/:id
{
  "name": "New Name for Security Key"
}

// Delete passkey
DELETE /auth/passkey/:id

MFA Integration

The passkey plugin seamlessly integrates with the MFA plugin:

// Setup both plugins
auth := authsome.New(
    db,
    authsome.WithPlugins(
        passkey.NewPlugin(),
        mfa.NewPlugin(),
    ),
)

// Passkeys can then be enrolled as an MFA factor
POST /auth/mfa/factors/enroll
{
  "type": "webauthn",
  "name": "YubiKey 5",
  "metadata": {
    "authenticatorType": "cross-platform"
  }
}

// And verified during MFA challenges
POST /auth/mfa/verify
{
  "challengeId": "...",
  "factorId": "...",
  "data": {
    "credentialResponse": { /* WebAuthn assertion */ }
  }
}

Client-Side Integration

Basic JavaScript Example
// Registration
async function registerPasskey(userId, name) {
  // 1. Get registration options
  const beginResp = await fetch('/auth/passkey/register/begin', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ userId, name })
  });
  const { options } = await beginResp.json();

  // 2. Create credential
  const credential = await navigator.credentials.create({
    publicKey: options
  });

  // 3. Finish registration
  const finishResp = await fetch('/auth/passkey/register/finish', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      userId,
      name,
      response: credential
    })
  });

  return await finishResp.json();
}

// Authentication
async function loginWithPasskey(userId) {
  // 1. Get authentication options
  const beginResp = await fetch('/auth/passkey/login/begin', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ userId })
  });
  const { options } = await beginResp.json();

  // 2. Get credential
  const credential = await navigator.credentials.get({
    publicKey: options
  });

  // 3. Finish authentication
  const finishResp = await fetch('/auth/passkey/login/finish', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      response: credential,
      remember: true
    })
  });

  return await finishResp.json();
}
Conditional UI (Autofill)
// Enable passkey autofill in login form
async function setupPasskeyAutofill() {
  if (!window.PublicKeyCredential?.isConditionalMediationAvailable) {
    return;
  }

  const available = await PublicKeyCredential.isConditionalMediationAvailable();
  if (!available) return;

  // Get options for discoverable login
  const resp = await fetch('/auth/passkey/login/begin', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({})  // No userId
  });
  const { options } = await resp.json();

  // Start conditional mediation
  const credential = await navigator.credentials.get({
    publicKey: options,
    mediation: 'conditional'
  });

  // Finish login
  const loginResp = await fetch('/auth/passkey/login/finish', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      response: credential,
      remember: true
    })
  });

  const result = await loginResp.json();
  window.location.href = '/dashboard';
}

// HTML form with autocomplete
<input
  type="text"
  name="username"
  autocomplete="username webauthn"
  placeholder="Email or passkey"
/>

Architecture

The passkey plugin follows clean architecture principles:

plugin.go           → Plugin registration and initialization
service.go          → Business logic and WebAuthn operations
handlers.go         → HTTP request handling
webauthn.go         → WebAuthn library wrapper
user_adapter.go     → WebAuthn User interface implementation
challenge_store.go  → Challenge session management
request_types.go    → Request DTOs with validation
response_types.go   → Response DTOs
Multi-Tenancy

Passkeys are automatically scoped to:

  • App: Platform tenant
  • Organization: User-created workspace (optional)

This ensures complete data isolation in multi-tenant deployments.

Security Considerations

Best Practices
  1. HTTPS Required: WebAuthn only works over HTTPS (or localhost for development)
  2. RP ID Must Match: Set RPID to your domain (e.g., "example.com")
  3. Origins Whitelist: Configure all allowed origins in RPOrigins
  4. User Verification: Use "required" for sensitive operations
  5. Sign Count Tracking: Monitor for cloned authenticators
  6. Attestation: Use "direct" for high-security environments
Threat Model

The passkey plugin protects against:

  • ✅ Phishing (origin-bound credentials)
  • ✅ Credential stuffing (no passwords)
  • ✅ Replay attacks (sign count tracking)
  • ✅ Man-in-the-middle (cryptographic binding)
  • ✅ Cloned authenticators (sign count validation)
  • ✅ Database breaches (public keys only)

Troubleshooting

Common Issues

Issue: Registration fails with "rpID mismatch" Solution: Ensure RPID matches your domain exactly. For https://app.example.com, use example.com as RPID.

Issue: "NotAllowedError: The operation either timed out or was not allowed" Solution: Check user verification requirements. For Touch ID/Windows Hello, ensure "preferred" or "discouraged" is used.

Issue: Challenge expired Solution: Increase timeout in configuration. Default is 5 minutes (300000ms).

Issue: Passkeys not appearing in autofill Solution: Ensure requireResidentKey: true during registration and use conditional mediation.

Browser Support

Browser Platform Auth Cross-Platform Conditional UI
Chrome 67+ ✅ (108+)
Safari 14+ ✅ (16+)
Firefox 60+
Edge 18+ ✅ (108+)

Migration from Beta

If you were using the beta version, run the database migration:

# The migration adds required WebAuthn fields
./authsome-cli migrate up

Note: Existing passkey records from beta will need to be re-registered as they lack cryptographic public keys.

References

Specifications
Guides
Security

Support

For questions, issues, or feature requests:

  • GitHub Issues: Report bugs or request features
  • Documentation: See main AuthSome docs for additional integration examples
  • Security Issues: Please report security vulnerabilities privately

License

Same as main AuthSome project - see LICENSE file.


Last Updated: November 20, 2025
Status: ✅ Production Ready
Maintainers: AuthSome Core Team

Documentation

Overview

Package passkey provides WebAuthn/FIDO2 passkey authentication.

✅ PRODUCTION READY ✅

This plugin provides enterprise-grade WebAuthn/FIDO2 authentication with: - Full cryptographic challenge generation and verification - Attestation verification during registration - Signature verification during authentication - Sign count tracking for replay attack prevention - Resident key / discoverable credential support - App and organization scoping for multi-tenancy - Both standalone passwordless and MFA integration modes

See plugins/passkey/README.md for complete documentation and usage examples.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ParseAuthenticatorAttachment

func ParseAuthenticatorAttachment(s string) protocol.AuthenticatorAttachment

ParseAuthenticatorAttachment converts string to protocol type

func ParseConveyancePreference

func ParseConveyancePreference(s string) protocol.ConveyancePreference

ParseConveyancePreference converts string to protocol type

func ParseResidentKeyRequirement

func ParseResidentKeyRequirement(required bool) protocol.ResidentKeyRequirement

ParseResidentKeyRequirement converts bool to protocol type

func ParseUserVerificationRequirement

func ParseUserVerificationRequirement(s string) protocol.UserVerificationRequirement

ParseUserVerificationRequirement converts string to protocol type

Types

type BeginLoginRequest

type BeginLoginRequest struct {
	UserID           string `json:"userId,omitempty" validate:"omitempty,xid"` // Optional for discoverable credentials
	UserVerification string `json:"userVerification,omitempty" validate:"omitempty,oneof=required preferred discouraged"`
}

BeginLoginRequest initiates passkey authentication

type BeginLoginResponse

type BeginLoginResponse struct {
	Options   interface{}   `json:"options"`   // WebAuthn PublicKeyCredentialRequestOptions
	Challenge string        `json:"challenge"` // Base64URL encoded challenge
	Timeout   time.Duration `json:"timeout"`   // Timeout in milliseconds
}

BeginLoginResponse contains WebAuthn authentication options

type BeginRegisterRequest

type BeginRegisterRequest struct {
	UserID             string `json:"userId" validate:"required,xid"`
	Name               string `json:"name,omitempty" validate:"omitempty,min=1,max=100"`
	AuthenticatorType  string `json:"authenticatorType,omitempty" validate:"omitempty,oneof=platform cross-platform"`
	RequireResidentKey bool   `json:"requireResidentKey"`
	UserVerification   string `json:"userVerification,omitempty" validate:"omitempty,oneof=required preferred discouraged"`
}

BeginRegisterRequest initiates passkey registration

type BeginRegisterResponse

type BeginRegisterResponse struct {
	Options   interface{}   `json:"options"`   // WebAuthn PublicKeyCredentialCreationOptions
	Challenge string        `json:"challenge"` // Base64URL encoded challenge
	UserID    string        `json:"userId"`
	Timeout   time.Duration `json:"timeout"` // Timeout in milliseconds
}

BeginRegisterResponse contains WebAuthn registration options

type ChallengeSession

type ChallengeSession struct {
	Challenge   []byte
	UserID      xid.ID
	SessionData interface{} // webauthn.SessionData
	CreatedAt   time.Time
	ExpiresAt   time.Time
}

ChallengeSession stores WebAuthn challenge data with timeout

type ChallengeStore

type ChallengeStore interface {
	// Store saves a challenge session with expiration
	Store(ctx context.Context, sessionID string, session *ChallengeSession) error

	// Get retrieves a challenge session
	Get(ctx context.Context, sessionID string) (*ChallengeSession, error)

	// Delete removes a challenge session
	Delete(ctx context.Context, sessionID string) error

	// CleanupExpired removes all expired challenge sessions
	CleanupExpired(ctx context.Context) error
}

ChallengeStore defines the interface for challenge session storage

type Config

type Config struct {
	RPID                    string        `json:"rpid" yaml:"rpid"`
	RPName                  string        `json:"rpname" yaml:"rpname"`
	RPOrigins               []string      `json:"rporigins" yaml:"rporigins"`
	Timeout                 time.Duration `json:"timeout" yaml:"timeout"`                                 // milliseconds
	UserVerification        string        `json:"userverification" yaml:"userverification"`               // required, preferred, discouraged
	AttestationType         string        `json:"attestationtype" yaml:"attestationtype"`                 // none, indirect, direct
	RequireResidentKey      bool          `json:"requireresidentkey" yaml:"requireresidentkey"`           // require resident keys
	AuthenticatorAttachment string        `json:"authenticatorattachment" yaml:"authenticatorattachment"` // platform, cross-platform, or empty
	ChallengeStorage        string        `json:"challengestorage" yaml:"challengestorage"`               // memory or redis (future)
}

Config contains WebAuthn/FIDO2 configuration

type DeletePasskeyRequest

type DeletePasskeyRequest struct {
	ID string `path:"id" validate:"required,xid"`
}

DeletePasskeyRequest deletes a passkey

type ErrorResponse

type ErrorResponse = responses.ErrorResponse

Response types - use shared responses from core

type FinishLoginRequest

type FinishLoginRequest struct {
	Response map[string]interface{} `json:"response" validate:"required"` // WebAuthn PublicKeyCredential assertion
	Remember bool                   `json:"remember"`
}

FinishLoginRequest completes passkey authentication

type FinishRegisterRequest

type FinishRegisterRequest struct {
	UserID   string                 `json:"userId" validate:"required,xid"`
	Name     string                 `json:"name,omitempty" validate:"omitempty,min=1,max=100"`
	Response map[string]interface{} `json:"response" validate:"required"` // WebAuthn PublicKeyCredential
}

FinishRegisterRequest completes passkey registration with credential attestation

type FinishRegisterResponse

type FinishRegisterResponse struct {
	PasskeyID    string    `json:"passkeyId"`
	Name         string    `json:"name"`
	Status       string    `json:"status"`
	CreatedAt    time.Time `json:"createdAt"`
	CredentialID string    `json:"credentialId"`
}

FinishRegisterResponse contains registered passkey information

type GetPasskeyRequest

type GetPasskeyRequest struct {
	ID string `path:"id" validate:"required,xid"`
}

GetPasskeyRequest retrieves a single passkey

type Handler

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

func NewHandler

func NewHandler(s *Service) *Handler

func (*Handler) BeginLogin

func (h *Handler) BeginLogin(c forge.Context) error

BeginLogin initiates passkey authentication with WebAuthn challenge

func (*Handler) BeginRegister

func (h *Handler) BeginRegister(c forge.Context) error

BeginRegister initiates passkey registration with WebAuthn challenge

func (*Handler) Delete

func (h *Handler) Delete(c forge.Context) error

Delete removes a passkey

func (*Handler) FinishLogin

func (h *Handler) FinishLogin(c forge.Context) error

FinishLogin completes passkey authentication with signature verification

func (*Handler) FinishRegister

func (h *Handler) FinishRegister(c forge.Context) error

FinishRegister completes passkey registration with attestation verification

func (*Handler) Get

func (h *Handler) Get(c forge.Context) error

Get retrieves a single passkey by ID

func (*Handler) List

func (h *Handler) List(c forge.Context) error

List retrieves all passkeys for a user

func (*Handler) Update

func (h *Handler) Update(c forge.Context) error

Update updates a passkey's metadata (name)

type ListPasskeysRequest

type ListPasskeysRequest struct {
	UserID string `query:"userId" validate:"required,xid"`
}

ListPasskeysRequest retrieves user's passkeys

type ListPasskeysResponse

type ListPasskeysResponse struct {
	Passkeys []PasskeyInfo `json:"passkeys"`
	Count    int           `json:"count"`
}

ListPasskeysResponse contains list of user passkeys

type LoginResponse

type LoginResponse struct {
	User        interface{} `json:"user"`
	Session     interface{} `json:"session"`
	Token       string      `json:"token"`
	PasskeyUsed string      `json:"passkeyUsed"` // ID of the passkey that was used
}

LoginResponse contains authentication result with session

type MemoryChallengeStore

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

MemoryChallengeStore is an in-memory implementation of ChallengeStore For production with multiple instances, use RedisChallengeStore instead

func NewMemoryChallengeStore

func NewMemoryChallengeStore(timeout time.Duration) *MemoryChallengeStore

NewMemoryChallengeStore creates a new in-memory challenge store

func (*MemoryChallengeStore) CleanupExpired

func (s *MemoryChallengeStore) CleanupExpired(ctx context.Context) error

CleanupExpired removes all expired sessions

func (*MemoryChallengeStore) Delete

func (s *MemoryChallengeStore) Delete(ctx context.Context, sessionID string) error

Delete removes a challenge session

func (*MemoryChallengeStore) Get

func (s *MemoryChallengeStore) Get(ctx context.Context, sessionID string) (*ChallengeSession, error)

Get retrieves a challenge session

func (*MemoryChallengeStore) Store

func (s *MemoryChallengeStore) Store(ctx context.Context, sessionID string, session *ChallengeSession) error

Store saves a challenge session

type MessageResponse

type MessageResponse = responses.MessageResponse

type PasskeyErrorResponse

type PasskeyErrorResponse = ErrorResponse

Legacy response type aliases for backward compatibility Use the proper types defined in response_types.go instead

type PasskeyInfo

type PasskeyInfo struct {
	ID                string     `json:"id"`
	Name              string     `json:"name"`
	CredentialID      string     `json:"credentialId"`
	AAGUID            string     `json:"aaguid,omitempty"`
	AuthenticatorType string     `json:"authenticatorType"` // "platform" or "cross-platform"
	CreatedAt         time.Time  `json:"createdAt"`
	LastUsedAt        *time.Time `json:"lastUsedAt,omitempty"`
	SignCount         uint32     `json:"signCount"`
	IsResidentKey     bool       `json:"isResidentKey"`
}

PasskeyInfo represents detailed passkey information

type PasskeyListResponse

type PasskeyListResponse = ListPasskeysResponse

type PasskeyLoginOptionsResponse

type PasskeyLoginOptionsResponse = BeginLoginResponse

type PasskeyLoginResponse

type PasskeyLoginResponse = LoginResponse

type PasskeyRegistrationOptionsResponse

type PasskeyRegistrationOptionsResponse = BeginRegisterResponse

type PasskeyStatusResponse

type PasskeyStatusResponse = StatusResponse

type Plugin

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

func NewPlugin

func NewPlugin(opts ...PluginOption) *Plugin

NewPlugin creates a new passkey plugin instance with optional configuration

func (*Plugin) ID

func (p *Plugin) ID() string

func (*Plugin) Init

func (p *Plugin) Init(authInst core.Authsome) error

func (*Plugin) Migrate

func (p *Plugin) Migrate() error

func (*Plugin) RegisterHooks

func (p *Plugin) RegisterHooks(_ *hooks.HookRegistry) error

func (*Plugin) RegisterRoutes

func (p *Plugin) RegisterRoutes(router forge.Router) error

func (*Plugin) RegisterServiceDecorators

func (p *Plugin) RegisterServiceDecorators(_ *registry.ServiceRegistry) error

type PluginOption

type PluginOption func(*Plugin)

PluginOption is a functional option for configuring the passkey plugin

func WithAttestationType

func WithAttestationType(attestation string) PluginOption

WithAttestationType sets the attestation conveyance preference

func WithAuthenticatorAttachment

func WithAuthenticatorAttachment(attachment string) PluginOption

WithAuthenticatorAttachment sets the authenticator attachment preference

func WithChallengeStorage

func WithChallengeStorage(storage string) PluginOption

WithChallengeStorage sets the challenge storage backend

func WithDefaultConfig

func WithDefaultConfig(cfg Config) PluginOption

WithDefaultConfig sets the default configuration for the plugin

func WithRPID

func WithRPID(rpID string) PluginOption

WithRPID sets the Relying Party ID

func WithRPName

func WithRPName(rpName string) PluginOption

WithRPName sets the Relying Party Name

func WithRPOrigins

func WithRPOrigins(origins []string) PluginOption

WithRPOrigins sets the allowed origins for WebAuthn

func WithRequireResidentKey

func WithRequireResidentKey(required bool) PluginOption

WithRequireResidentKey sets whether resident keys are required

func WithTimeout

func WithTimeout(timeout time.Duration) PluginOption

WithTimeout sets the WebAuthn timeout

func WithUserVerification

func WithUserVerification(requirement string) PluginOption

WithUserVerification sets the user verification requirement

type RedisChallengeStore

type RedisChallengeStore struct {
}

RedisChallengeStore is a Redis-backed implementation of ChallengeStore This is recommended for production environments with multiple instances

type Service

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

Service provides passkey/WebAuthn operations with full cryptographic verification

func NewService

func NewService(db *bun.DB, userSvc *user.Service, authSvc *auth.Service, auditSvc *audit.Service, cfg Config) (*Service, error)

NewService creates a new passkey service with WebAuthn support

func (*Service) BeginDiscoverableLogin

func (s *Service) BeginDiscoverableLogin(ctx context.Context, req BeginLoginRequest) (*BeginLoginResponse, error)

BeginDiscoverableLogin initiates authentication for discoverable credentials (usernameless)

func (*Service) BeginLogin

func (s *Service) BeginLogin(ctx context.Context, userID xid.ID, req BeginLoginRequest) (*BeginLoginResponse, error)

BeginLogin initiates WebAuthn authentication challenge

func (*Service) BeginRegistration

func (s *Service) BeginRegistration(ctx context.Context, userID xid.ID, req BeginRegisterRequest) (*BeginRegisterResponse, error)

BeginRegistration initiates WebAuthn passkey registration with cryptographic challenge

func (*Service) Delete

func (s *Service) Delete(ctx context.Context, passkeyID xid.ID, ip, ua string) error

Delete removes a passkey (app and org scoped)

func (*Service) FinishLogin

func (s *Service) FinishLogin(ctx context.Context, credentialResponse []byte, remember bool, ip, ua string) (*LoginResponse, error)

FinishLogin completes authentication with signature verification and creates session

func (*Service) FinishRegistration

func (s *Service) FinishRegistration(ctx context.Context, userID xid.ID, credentialResponse []byte, name, ip, ua string) (*FinishRegisterResponse, error)

FinishRegistration completes passkey registration with attestation verification

func (*Service) List

func (s *Service) List(ctx context.Context, userID xid.ID) (*ListPasskeysResponse, error)

List retrieves all passkeys for a user (app and org scoped)

func (*Service) Update

func (s *Service) Update(ctx context.Context, passkeyID xid.ID, name string) (*UpdatePasskeyResponse, error)

Update updates a passkey's metadata (primarily name)

type StatusResponse

type StatusResponse = responses.StatusResponse

type UpdatePasskeyRequest

type UpdatePasskeyRequest struct {
	ID   string `path:"id" validate:"required,xid"`
	Name string `json:"name" validate:"required,min=1,max=100"`
}

UpdatePasskeyRequest updates passkey metadata (name)

type UpdatePasskeyResponse

type UpdatePasskeyResponse struct {
	PasskeyID string    `json:"passkeyId"`
	Name      string    `json:"name"`
	UpdatedAt time.Time `json:"updatedAt"`
}

UpdatePasskeyResponse contains updated passkey information

type UserAdapter

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

UserAdapter adapts AuthSome User to WebAuthn User interface

func NewUserAdapter

func NewUserAdapter(userID xid.ID, userName, displayName string, passkeys []schema.Passkey) *UserAdapter

NewUserAdapter creates a new user adapter for WebAuthn

func (*UserAdapter) AddCredential

func (u *UserAdapter) AddCredential(cred webauthn.Credential)

AddCredential adds a new credential to the user adapter

func (*UserAdapter) UpdateCredential

func (u *UserAdapter) UpdateCredential(credentialID []byte, signCount uint32)

UpdateCredential updates an existing credential's sign count

func (*UserAdapter) WebAuthnCredentials

func (u *UserAdapter) WebAuthnCredentials() []webauthn.Credential

WebAuthnCredentials returns the user's credentials

func (*UserAdapter) WebAuthnDisplayName

func (u *UserAdapter) WebAuthnDisplayName() string

WebAuthnDisplayName returns the user's display name

func (*UserAdapter) WebAuthnID

func (u *UserAdapter) WebAuthnID() []byte

WebAuthnID returns the user's ID as bytes

func (*UserAdapter) WebAuthnIcon

func (u *UserAdapter) WebAuthnIcon() string

WebAuthnIcon returns the user's icon URL (optional)

func (*UserAdapter) WebAuthnName

func (u *UserAdapter) WebAuthnName() string

WebAuthnName returns the user's username

type WebAuthnWrapper

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

WebAuthnWrapper wraps the go-webauthn/webauthn library for easier use

func NewWebAuthnWrapper

func NewWebAuthnWrapper(cfg Config) (*WebAuthnWrapper, error)

NewWebAuthnWrapper creates a new WebAuthn wrapper with configuration

func (*WebAuthnWrapper) BeginDiscoverableLogin

func (w *WebAuthnWrapper) BeginDiscoverableLogin(opts ...webauthn.LoginOption) (*protocol.CredentialAssertion, *webauthn.SessionData, error)

BeginDiscoverableLogin initiates authentication for discoverable credentials (usernameless)

func (*WebAuthnWrapper) BeginLogin

BeginLogin initiates WebAuthn authentication

func (*WebAuthnWrapper) BeginRegistration

BeginRegistration initiates WebAuthn credential registration

func (*WebAuthnWrapper) FinishLogin

FinishLogin completes WebAuthn authentication with credential verification

func (*WebAuthnWrapper) FinishRegistration

func (w *WebAuthnWrapper) FinishRegistration(user webauthn.User, session webauthn.SessionData, response *protocol.ParsedCredentialCreationData) (*webauthn.Credential, error)

FinishRegistration completes WebAuthn credential registration

Jump to

Keyboard shortcuts

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