The passkey plugin implements the WebAuthn specification (FIDO2), enabling authentication with biometrics (Face ID, Touch ID, Windows Hello) or hardware security keys (YubiKey). Passkeys are phishing-resistant and require no password.
Setup01
import (
"github.com/xraph/authsome"
"github.com/xraph/authsome/plugins/passkey"
)
eng, err := authsome.New(
authsome.WithStore(store),
authsome.WithPlugin(passkey.New(passkey.Config{
// Relying Party ID — must match the effective domain of your origin.
RPID: "myapp.com",
// Relying Party display name — shown in the browser passkey UI.
RPDisplayName: "My Application",
// Allowed origins — must include your frontend's exact origin
// (scheme + host + port). For production, set explicitly.
RPOrigins: []string{
"https://myapp.com",
"https://www.myapp.com",
},
})),
)Local development
For RPID: "localhost" (or "127.0.0.1") the plugin auto-trusts the request's Origin header on every ceremony, as long as its host matches the configured RPID. That means any localhost dev port — http://localhost:3000, http://localhost:5173, etc. — works without listing it in RPOrigins. The host check still rejects foreign origins, so this shortcut only loosens the port requirement, never the host.
For non-localhost RPIDs, behavior is strict: the browser's origin must match an entry in RPOrigins (scheme, host, and port included) exactly.
Dynamic origins setting
passkey.rp_origins ([]string) is a dynamic setting that adds extra allowed origins on top of Config.RPOrigins. Use it when origins differ between environments and you'd rather not rebuild the binary — e.g. set it from the dashboard or via your settings store for staging vs. prod.
The passkey plugin uses the engine's ceremony.Store to hold in-progress registration and authentication state between the challenge and response calls. The default in-memory store is used when none is configured by the engine.
FIDO2/WebAuthn overview02
WebAuthn uses public-key cryptography:
During registration, the authenticator generates a key pair. The public key is stored on the server. The private key never leaves the device.
During authentication, the server sends a challenge. The authenticator signs it with the private key. The server verifies the signature with the stored public key.
Because the private key is device-bound and the challenge is domain-scoped, passkeys are immune to phishing — a fake site cannot obtain a valid authentication signature.
Registration ceremony03
The registration flow runs when an existing user wants to add a passkey to their account.
Begin registration: The server generates a challenge and public key credential creation options.
POST /v1/auth/passkey/register/begin
Request headers must include Authorization: Bearer {session_token} (user must be authenticated).
Response includes the WebAuthn PublicKeyCredentialCreationOptions JSON:
{
"challenge": "base64url-encoded-random-challenge",
"rp": {"id": "myapp.com", "name": "My Application"},
"user": {"id": "base64url-user-id", "name": "[email protected]", "displayName": "Alice"},
"pubKeyCredParams": [{"type": "public-key", "alg": -7}],
"timeout": 60000,
"attestation": "none",
"excludeCredentials": [...]
}Create passkey on device: Your JavaScript calls navigator.credentials.create(options). The browser/OS prompts the user for biometric or PIN confirmation. On success, a PublicKeyCredential is returned.
Finish registration: Submit the credential to the server.
POST /v1/auth/passkey/register/finish
{
"credential": {
"id": "base64url-credential-id",
"rawId": "base64url-raw-id",
"type": "public-key",
"response": {
"attestationObject": "base64url-attestation",
"clientDataJSON": "base64url-client-data"
}
}
}The engine verifies the attestation, stores the Passkey credential (ID prefix: apsk_), and returns the credential metadata.
Authentication ceremony04
The authentication flow allows users to sign in without a password.
Begin authentication: The server generates a challenge.
POST /v1/auth/passkey/authenticate/begin
{"email": "[email protected]", "app_id": "myapp"}Response includes the WebAuthn PublicKeyCredentialRequestOptions:
{
"challenge": "base64url-random-challenge",
"timeout": 60000,
"rpId": "myapp.com",
"allowCredentials": [
{"type": "public-key", "id": "base64url-credential-id"}
],
"userVerification": "required"
}Sign the challenge on device: Your JavaScript calls navigator.credentials.get(options). The browser prompts biometric confirmation and signs the challenge.
Finish authentication: Submit the assertion.
POST /v1/auth/passkey/authenticate/finish
{
"credential": {
"id": "base64url-credential-id",
"rawId": "base64url-raw-id",
"type": "public-key",
"response": {
"authenticatorData": "base64url-auth-data",
"clientDataJSON": "base64url-client-data",
"signature": "base64url-signature",
"userHandle": "base64url-user-id"
}
}
}The engine verifies the signature, updates the passkey's SignCount, and creates a session. Returns user + session.
Credential management05
Retrieve all passkeys for a user:
passkeys, err := engine.ListUserPasskeys(ctx, userID)Each Passkey record:
type Passkey struct {
ID id.PasskeyID `json:"id"`
UserID id.UserID `json:"user_id"`
AppID id.AppID `json:"app_id"`
CredentialID []byte `json:"credential_id"`
PublicKey []byte `json:"-"` // never serialized
AAGUID string `json:"aaguid"` // authenticator model ID
Name string `json:"name"` // user-assigned name
SignCount uint32 `json:"sign_count"`
Transports []string `json:"transports"` // "internal", "usb", "nfc", "ble"
BackupEligible bool `json:"backup_eligible"`
BackupState bool `json:"backup_state"`
LastUsedAt time.Time `json:"last_used_at"`
CreatedAt time.Time `json:"created_at"`
}Let users rename or delete their passkeys:
// Rename a passkey
engine.UpdatePasskeyName(ctx, passkeyID, "MacBook Touch ID")
// Remove a passkey (the user must have another sign-in method)
engine.DeletePasskey(ctx, passkeyID)API routes06
| Method | Path | Description |
POST | /passkey/register/begin | Begin passkey registration ceremony |
POST | /passkey/register/finish | Finish passkey registration |
POST | /passkey/authenticate/begin | Begin passkey authentication ceremony |
POST | /passkey/authenticate/finish | Finish authentication and return session |
GET | /passkey | List all passkeys for the current user |
PATCH | /passkey/{id} | Rename a passkey |
DELETE | /passkey/{id} | Delete a passkey |