VettiGuard

VettiGuard Passkeys as a Service

Browse the complete reference in the browser, copy request examples, and follow the documented validation contract without opening packaged Markdown files.

Complete browser reference

VettiGuard Passkeys as a Service

VettiGuard Passkeys as a Service lets a protected web application add phishing-resistant WebAuthn authentication without exposing its VettiGuard server credential to the browser.

The relying application remains the account system of record. VettiGuard stores only a keyed, site-scoped representation of the application's subject_id and returns a pairwise subject_ref for correlation. Raw subject identifiers are not persisted by this feature.

Security boundary

  • Use a scoped server credential with site.passkeys.
  • Registration and authentication ceremonies require user verification (UV).
  • Registration requests privacy-preserving attestation: none and accepts ES256 or RS256 credentials.
  • Passkeys are bound to a verified protected-site origin and its exact WebAuthn RP ID.
  • Production origins must use HTTPS. http://localhost and *.localhost are accepted only for local development.
  • Challenges expire after 5 minutes and can be completed once.
  • Authentication evidence uses the vgpk.* format, expires after 5 minutes, and is one-use by default.
  • Authentication counters are checked where the authenticator supplies a non-zero counter. A non-advancing counter is rejected.
  • The relying application's raw subject_id is not stored.

1. Register a passkey

Your backend requests registration options:

POST /api/v1/passkeys/registration/options
Authorization: Bearer vgk_site_...
Content-Type: application/json
{
  "subject_id": "customer-1842",
  "origin": "https://app.example.com",
  "display_name": "Customer account"
}

VettiGuard returns a normal PublicKeyCredentialCreationOptions object under publicKey, plus a short-lived challenge_token.

Your backend passes only publicKey and the opaque challenge token to the browser. The browser invokes:

const credential = await navigator.credentials.create({ publicKey });

Your backend then submits the browser response to:

POST /api/v1/passkeys/registration/complete

The response contains a VettiGuard credential UUID and the pairwise subject_ref. Store those references with the relying application's account if you need credential-management UI.

2. Authenticate with a known subject

POST /api/v1/passkeys/authentication/options
{
  "subject_id": "customer-1842",
  "origin": "https://app.example.com",
  "action": "sign-in",
  "context": "login-9942"
}

The returned publicKey contains the active credential descriptors for that subject.

The browser invokes:

const credential = await navigator.credentials.get({ publicKey });

Your backend sends the response to:

POST /api/v1/passkeys/authentication/complete

A successful response includes:

{
  "authenticated": true,
  "authentication_method": "passkey",
  "user_verification": true,
  "subject_ref": "vgsub_...",
  "credential_id": "...",
  "evidence": "vgpk...."
}

3. Discoverable/usernameless sign-in

Omit subject_id from the authentication-options request. VettiGuard omits allowCredentials, allowing the browser/authenticator to select a discoverable passkey.

For this mode VettiGuard requires the WebAuthn assertion to return a valid user handle. The completed response returns only the pairwise subject_ref; the application should map that value to its account record. VettiGuard does not return or retain the application's original subject identifier.

4. Verify passkey evidence

Passkey completion is already a server-authenticated VettiGuard response. For workflows that need portable, action-bound evidence, verify the short-lived vgpk.* token:

POST /api/v1/passkeys/evidence/verify
{
  "evidence": "vgpk.payload.signature",
  "action": "sign-in",
  "subject_id": "customer-1842",
  "context": "login-9942",
  "consume": true
}

By default evidence is consumed on successful verification. Set consume: false only for a read-only preflight that cannot authorize the protected action.

vgpk.* is VettiGuard authentication evidence, not a legal electronic signature.

5. Revoke a credential

POST /api/v1/passkeys/credentials/revoke
{
  "credential_id": "VETTIGUARD-CREDENTIAL-UUID",
  "reason": "customer-request"
}

Revocation is scoped to the protected site that owns the API credential.

Browser serialization

WebAuthn uses binary ArrayBuffer values. Convert binary values to base64url before sending them to your backend. The backend—not the browser—calls the VettiGuard API using the private site.passkeys credential.

Never embed a vgk_site_*, legacy site secret, or VettiGuard evidence-signing secret in JavaScript.

Privacy model

VettiGuard stores:

  • a keyed site/workspace-scoped subject hash;
  • a pairwise subject_ref;
  • the WebAuthn credential identifier and public key;
  • authenticator algorithm/counter/backup metadata;
  • bounded challenge/evidence state.

VettiGuard does not store the raw application subject_id for Passkeys as a Service.

Credential limit

A subject can have up to 10 active passkeys per protected site. Revoke unused credentials before registering additional credentials.

Error handling

ErrorMeaningHandling
passkey-origin-not-allowedOrigin is not registered for the protected siteFix site-domain configuration; do not retry unchanged
passkey-origin-requires-httpsNon-local origin is not HTTPSUse HTTPS
passkey-challenge-expired-or-consumedCeremony is stale or already completedStart a new ceremony
passkey-not-found-for-subjectKnown-subject authentication has no active passkeyOffer another governed authentication method
passkey-credential-limit-reachedSubject already has 10 active passkeysRevoke an old credential
passkey-service-unavailableRequired migration/storage unavailableFail closed and retry after service recovery

Migration

Apply:

202609210002_passkeys_as_a_service

before enabling the public endpoints.