VenddorDocumentação API
API atualAPI legada
Guia rápidoReferência completa
Carregando índice…
API atual/Referência completa/Autenticação avançada

Login com passkey

Entra com biometria ou chave de segurança (WebAuthn), sem digitar senha, em duas chamadas.

Uma passkey é uma credencial guardada no aparelho do cliente (Face ID, impressão digital, Windows Hello ou chave física) que substitui a senha. O padrão técnico é o WebAuthn: o servidor manda um desafio, o navegador pede a biometria e devolve uma prova assinada. A plataforma suporta passkeys de descoberta automática: o cliente não digita e-mail, o próprio aparelho escolhe a credencial.

O fluxo completo tem três partes, em três páginas:

  1. Cadastrar a passkey (cliente já logado, uma vez por aparelho).
  2. Entrar com a passkey (esta página).
  3. Listar e remover passkeys.

Há ainda a confirmação por biometria (step-up), para ações sensíveis.

Pontos principais
  • O login tem duas chamadas públicas: login/begin entrega um desafio e login/finish recebe a prova e devolve os tokens (mesmo formato do login com senha).
  • Cada cerimônia (ceremony_id) vale 5 minutos e uma única vez. Repetir o finish falha.
  • A passkey é presa ao domínio da loja (o rpId é o host da loja). Uma passkey cadastrada em minhaloja.io.venddor.com.br não vale em outra loja nem em outro domínio.
  • Quem chama as funções do navegador (navigator.credentials) é o seu front-end. Uma integração servidor a servidor não consegue usar passkeys.
POST/api/v1/auth/webauthn/login/beginPública

Iniciar o login com passkey

Gera o desafio. Não recebe corpo. O options.publicKey da resposta vai direto para navigator.credentials.get.

Exemplo de requisição

bash
curl -X POST "$BASE_URL/api/v1/auth/webauthn/login/begin"

Resposta

200Desafio criado
json
{
  "ceremony_id": "647bb41f-0aec-46ac-a2aa-9396f65ea311",
  "options": {
    "publicKey": {
      "challenge": "qrTUyCAThPPHs-5tfQvPMVj_nlVvoKMx9mbcVZK11v4",
      "timeout": 300000,
      "rpId": "minhaloja.io.venddor.com.br"
    }
  }
}

Campos da resposta

ceremony_iduuid
Identifica esta tentativa. Devolva no finish.
optionsobject
Opções WebAuthn. O objeto interno publicKey traz challenge (desafio em base64url), timeout (milissegundos) e rpId (host da loja, sem porta).
POST/api/v1/auth/webauthn/login/finishPública

Concluir o login com passkey

Verifica a prova do aparelho e abre a sessão.

Corpo da requisição

ceremony_iduuidobrigatório
O ceremony_id recebido no begin.
credentialobjectobrigatório
A resposta de navigator.credentials.get serializada em JSON (id, rawId, type, response).
accept_pending_sessionbooleanpadrão: false
Igual ao login com senha: sem isso, um cliente cujo cadastro aguarda aprovação da loja é recusado com account_pending_approval. Com true, ele recebe uma sessão restrita às rotas de cadastro.

Exemplo de requisição

bash
curl -X POST "$BASE_URL/api/v1/auth/webauthn/login/finish" \
  -H "Content-Type: application/json" \
  -d '{
  "ceremony_id": "647bb41f-0aec-46ac-a2aa-9396f65ea311",
  "credential": {
    "id": "AQIDBAUG...",
    "rawId": "AQIDBAUG...",
    "type": "public-key",
    "response": {
      "clientDataJSON": "eyJ0eXBl...",
      "authenticatorData": "SZYN5Y...",
      "signature": "MEUCIQ...",
      "userHandle": "M2YwYzJh..."
    }
  }
}'

No navegador, o conjunto das duas chamadas fica assim (navegadores recentes oferecem parseRequestOptionsFromJSON e toJSON, que evitam a conversão manual de base64url):

js
const begin = await (await fetch("/api/v1/auth/webauthn/login/begin", { method: "POST" })).json();
const publicKey = PublicKeyCredential.parseRequestOptionsFromJSON(begin.options.publicKey);
const cred = await navigator.credentials.get({ publicKey });
const res = await fetch("/api/v1/auth/webauthn/login/finish", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ ceremony_id: begin.ceremony_id, credential: cred.toJSON() }),
});
const tokens = await res.json();

Resposta

200Sessão aberta
json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "rt_9f2c1d7e4b...",
  "expires_at": "2026-10-11T21:30:00Z"
}

Campos da resposta

access_tokenstring
JWT de acesso. Veja Tokens.
refresh_tokenstring
Token para renovar a sessão em POST /api/v1/auth/refresh.
expires_atstring
Expiração do access_token (RFC 3339, UTC).
account_statusstring
pending_approval
Só aparece quando a sessão é restrita por aprovação pendente.

Erros

StatusQuandoExemplo de corpo
400Corpo do finish que não é JSON.{"error":"invalid body","detail":"..."}
401Prova inválida, cerimônia expirada ou já usada, ou passkey desconhecida.{"error":"login failed","detail":"..."}
403Cadastro aguardando aprovação e accept_pending_session ausente.{"error":"account_pending_approval"}
403Conta desativada.{"error":"account_disabled"}
429Limite de requisições da família /auth/.{"error":"rate limit exceeded","detail":"too many requests, please try again later"}
500Falha ao gerar o desafio no begin.{"error":"failed to begin login","detail":"..."}
Texto de detail

No 401, o campo detail é uma mensagem técnica (às vezes em português) pensada para depuração. Para o cliente, mostre uma mensagem sua ("não foi possível entrar com a passkey") e ofereça o login com senha como alternativa.

Veja também

  • Cadastrar passkey e confirmar por biometria
  • Listar e remover passkeys
  • Tokens e escopos
Anterior
Login por código (OTP)
Próxima
Cadastrar passkey e confirmar por biometria
Nesta página
  • Exemplo de requisição
  • Resposta
  • Exemplo de requisição
  • Resposta
  • Erros
  • Veja também