> ## Documentation Index
> Fetch the complete documentation index at: https://docs.unifokal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Login dos seus usuários

> Cadastro, login e segundo fator dos usuários do seu produto no mesmo widget, a troca do código com PKCE, o token verificado no seu servidor, a gestão de usuários, o M2M e a autorização fina (FGA).

## Login dos seus usuários

O mesmo widget que verifica identidade também cadastra e autentica os usuários do seu produto: e-mail e senha, código por e-mail ou SMS, segundo fator quando o risco pede, e recuperação de senha. Com módulos de pessoa no mesmo flow, o cadastro já nasce com a verificação de identidade. Cada organização e cada ambiente tem a sua própria base de usuários.

**Como liga.** No painel, ligue **Autenticação** no flow. O seu backend cria a sessão com o bloco `auth` (o `code_challenge` do PKCE S256), entrega o id da sessão à página, e o widget conduz o login. No fim ele entrega um código de uso único, que vale por um minuto e só serve com o `code_verifier` que ficou no seu backend e com a sua chave secreta.

**Verifique o token no seu servidor.** O token de acesso é um JWT (`typ at+jwt`, RS256) de vida curta, com uma chave por organização e ambiente. Aceite só RS256, confira o `typ`, o `iss` exato, o `aud` e o `exp`, e busque a chave pelo `kid` só no JWKS do emissor que você espera, nunca numa URL que vem dentro do token. O JWKS fica em `https://api.unifokal.com/v1/auth/t/{organization_id}/{ambiente}/.well-known/jwks.json`, sem chave nenhuma, e pode ser guardado em cache por dez minutos.

**Escopos.** Toda rota abaixo exige uma chave secreta criada com o escopo da rota: `auth:token`, `auth:users:read`, `auth:users:write`, `fga:check` ou `fga:write`. Chaves sem restrição não têm esses escopos. Toda resposta com token ou código sai com `Cache-Control: no-store`.

**POST** `/v1/auth/token`

Troca o código do widget (`authorization_code`) e renova (`refresh_token`). O refresh roda a cada uso: guarde sempre o último. Reapresentar um refresh antigo derruba a sessão inteira e dispara o webhook `auth.refresh_reuse_detected`. O corpo pode ir como formulário ou JSON.

```
curl -X POST https://api.unifokal.com/v1/auth/token \
  -H "Authorization: Bearer $UNIFOKAL_SECRET_KEY" \
  -d grant_type=authorization_code \
  -d code=$CODIGO_DO_WIDGET \
  -d code_verifier=$CODE_VERIFIER
```

```
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6InNnay4uLiJ9...",
  "token_type": "Bearer",
  "expires_in": 600,
  "refresh_token": "rt_01J...",
  "refresh_expires_in": 604800,
  "session_id": "ses_01J...",
  "amr": ["pwd"],
  "acr": "aal1",
  "user": { "id": "usr_01J...", "email": "ana@exemplo.com", "email_verified": true, "status": "active", "...": "..." },
  "verification_id": null
}
```

**Máquina a máquina.** Um cliente M2M, criado no painel com a API e os escopos que pode pedir, recebe o próprio token por `client_credentials` com o id e o segredo em `Authorization: Basic`, sem a chave secreta. A resposta não tem refresh: guarde o token até perto do `exp`.

```
curl -X POST https://api.unifokal.com/v1/auth/token \
  -u "$M2M_CLIENT_ID:$M2M_CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d scope="orders:read"
```

**Erros.** `400 invalid_grant` (código ou refresh inválido, usado, vencido, ou verificador errado), `400 invalid_scope` e `invalid_target` (M2M), `401 invalid_client` (M2M), `403 insufficient_scope`, `402 insufficient_balance` (M2M depois de saldo negativo prolongado; o login dos seus usuários nunca para por saldo) e os limites de uso `429 rate_limited`, `spend_cap_reached` e `volume_cap_reached`.

**POST** `/v1/auth/revoke`

Revoga a sessão de um refresh token (`token`) ou uma sessão pelo id (`session_id`), como no logout. Responde `200` mesmo para token desconhecido. O token de acesso já emitido vale até o `exp`: por isso ele é curto.

**GET** `/v1/auth/users`

Lista os usuários por cursor, com filtro por e-mail (sem diferença de caixa), telefone, `external_id` e estado. Nenhuma resposta traz senha, segredo ou hash. Por id, em `GET /v1/auth/users/{id}`, vêm o telefone inteiro, o metadado privado e os fatores.

**PATCH** `/v1/auth/users/{id}`

Grava o `external_id` (a sua referência do titular, sem forma de CPF, e-mail ou telefone), o `public_metadata` (vai no token), o `private_metadata` (nunca sai no token) e o estado: `disabled` e `locked` derrubam as sessões. `409 external_id_conflict` quando outro usuário do ambiente já tem a referência.

**DELETE** `/v1/auth/users/{id}`

Apaga o usuário, derruba as sessões e avisa pelo webhook `user.deleted`. As rotas `POST /v1/auth/users/{id}/unlock` (destrava) e `POST /v1/auth/users/{id}/sessions/revoke` (derruba todas as sessões) completam a gestão.

**POST** `/v1/fga/check`

Autorização fina no estilo Zanzibar: um modelo por organização e ambiente (`PUT /v1/fga/model`, cada gravação é uma versão nova e imutável), tuplas `objeto#relação@usuário` (`POST /v1/fga/tuples` grava e apaga numa transação, `GET /v1/fga/tuples` lê) e as perguntas: `check`, `batch-check` e `list-objects`. Uma escrita confirmada já vale para a checagem seguinte. Quando a resolução passa de um teto, a resposta é `422 fga_resolution_limit`, nunca um `false` silencioso.

```
curl -X POST https://api.unifokal.com/v1/fga/check \
  -H "Authorization: Bearer $UNIFOKAL_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "object": "document:42", "relation": "viewer", "user": "user:usr_01J..." }'

{ "allowed": true, "model_id": "fgm_01J..." }
```

**Webhooks.** `user.created`, `user.updated`, `user.deleted`, `user.locked`, `user.unlocked`, `auth.suspicious_sign_in`, `auth.refresh_reuse_detected` e `auth.compromised_password`, assinados como os demais e entregues no webhook do flow do cadastro. Login comum não gera webhook.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.