- Implement DatabasePasskeyProvider for WebAuthn/FIDO2 authentication. - Add methods for registration, authentication, and credential management. - Create unit tests for passkey provider functionalities. - Enhance DatabaseAuthenticator to support passkey authentication.
5.9 KiB
Passkey Authentication Quick Reference
Overview
Passkey authentication (WebAuthn/FIDO2) is now integrated into the DatabaseAuthenticator. This provides passwordless authentication using biometrics, security keys, or device credentials.
Setup
Database Schema
Run the passkey SQL schema (in database_schema.sql):
- Creates
user_passkey_credentialstable - Adds stored procedures for passkey operations
Go Code
// Create passkey provider
passkeyProvider := security.NewDatabasePasskeyProvider(db,
security.DatabasePasskeyProviderOptions{
RPID: "example.com",
RPName: "Example App",
RPOrigin: "https://example.com",
Timeout: 60000,
})
// Create authenticator with passkey support
auth := security.NewDatabaseAuthenticatorWithOptions(db,
security.DatabaseAuthenticatorOptions{
PasskeyProvider: passkeyProvider,
})
// Or add passkey to existing authenticator
auth = security.NewDatabaseAuthenticator(db).WithPasskey(passkeyProvider)
Registration Flow
Backend - Step 1: Begin Registration
options, err := auth.BeginPasskeyRegistration(ctx,
security.PasskeyBeginRegistrationRequest{
UserID: 1,
Username: "alice",
DisplayName: "Alice Smith",
})
// Send options to client as JSON
Frontend - Step 2: Create Credential
// Convert options from server
options.challenge = base64ToArrayBuffer(options.challenge);
options.user.id = base64ToArrayBuffer(options.user.id);
// Create credential
const credential = await navigator.credentials.create({
publicKey: options
});
// Send credential back to server
Backend - Step 3: Complete Registration
credential, err := auth.CompletePasskeyRegistration(ctx,
security.PasskeyRegisterRequest{
UserID: 1,
Response: clientResponse,
ExpectedChallenge: storedChallenge,
CredentialName: "My iPhone",
})
Authentication Flow
Backend - Step 1: Begin Authentication
options, err := auth.BeginPasskeyAuthentication(ctx,
security.PasskeyBeginAuthenticationRequest{
Username: "alice", // Optional for resident key
})
// Send options to client as JSON
Frontend - Step 2: Get Credential
// Convert options from server
options.challenge = base64ToArrayBuffer(options.challenge);
// Get credential
const credential = await navigator.credentials.get({
publicKey: options
});
// Send assertion back to server
Backend - Step 3: Complete Authentication
loginResponse, err := auth.LoginWithPasskey(ctx,
security.PasskeyLoginRequest{
Response: clientAssertion,
ExpectedChallenge: storedChallenge,
Claims: map[string]any{
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
},
})
// Returns session token and user info
Credential Management
List Credentials
credentials, err := auth.GetPasskeyCredentials(ctx, userID)
Update Credential Name
err := auth.UpdatePasskeyCredentialName(ctx, userID, credentialID, "New Name")
Delete Credential
err := auth.DeletePasskeyCredential(ctx, userID, credentialID)
HTTP Endpoints Example
POST /api/passkey/register/begin
Request: {user_id, username, display_name}
Response: PasskeyRegistrationOptions
POST /api/passkey/register/complete
Request: {user_id, response, credential_name}
Response: PasskeyCredential
POST /api/passkey/login/begin
Request: {username} (optional)
Response: PasskeyAuthenticationOptions
POST /api/passkey/login/complete
Request: {response}
Response: LoginResponse with session token
GET /api/passkey/credentials
Response: Array of PasskeyCredential
DELETE /api/passkey/credentials/{id}
Request: {credential_id}
Response: 204 No Content
Database Stored Procedures
resolvespec_passkey_store_credential- Store new credentialresolvespec_passkey_get_credential- Get credential by IDresolvespec_passkey_get_user_credentials- Get all user credentialsresolvespec_passkey_update_counter- Update sign counter (clone detection)resolvespec_passkey_delete_credential- Delete credentialresolvespec_passkey_update_name- Update credential nameresolvespec_passkey_get_credentials_by_username- Get credentials for login
Security Features
- Clone Detection: Sign counter validation detects credential cloning
- Attestation Support: Stores attestation type (none, indirect, direct)
- Transport Options: Tracks authenticator transports (usb, nfc, ble, internal)
- Backup State: Tracks if credential is backed up/synced
- User Verification: Supports preferred/required user verification
Important Notes
-
WebAuthn Library: Current implementation is simplified. For production, use a proper WebAuthn library like
github.com/go-webauthn/webauthnfor full verification. -
Challenge Storage: Store challenges securely in session/cache. Never expose challenges to client beyond initial request.
-
HTTPS Required: Passkeys only work over HTTPS (except localhost).
-
Browser Support: Check browser compatibility for WebAuthn API.
-
Relying Party ID: Must match your domain exactly.
Client-Side Helper Functions
function base64ToArrayBuffer(base64) {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
}
function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
Testing
Run tests: go test -v ./pkg/security -run Passkey
All passkey functionality includes comprehensive tests using sqlmock.