Docs›REST API›Tokens de API e abilities
REST API

Tokens de API e abilities

Tokens Bearer mad_api_* (tabela mad_api_token): emissão por artisan mad:api-token ou pela tela /app/tokens-api com reveal único, abilities descobertas da route table, TTL, revogação soft.

Todo acesso a uma rota mad.api passa por um token Bearer da tabela mad_api_token. O token nao e so uma credencial: ele carrega o escopo. Cada token e amarrado a um par user + unit, e a unit determina o tenant — e, em multi-database, o banco onde as queries daquela requisicao vao rodar. Nao existe "escolher a empresa" no cabecalho: quem emitiu o token ja decidiu.

Formato e armazenamento

O token em claro e mad_api_ + 48 caracteres hex (bin2hex(random_bytes(24))), gerado por MadApiTokenService::issue(). O banco guarda so o sha256 (token_hash) — o claro nunca persiste e nunca e logado. Perdeu, nao ha recuperacao: revoga e emite outro.

Coluna Conteudo
token_hash hash('sha256', $plain) — o unico material sensivel gravado.
token_prefix mad_api_ — so para exibicao/identificacao na listagem.
user_id / unit_id Escopo fixo do token (dono + unidade).
active '1' / '0' — mesmo contrato do mad_mcp_token.
abilities JSON array de permissoes finas; NULL = acesso total.
expires_at TTL; resolve() rejeita vencido.
revoked_at Soft-revoke — a linha fica para auditoria.
last_used_at Atualizado no uso, com granularidade de 300s (nao escreve a cada request).

Emitindo pela linha de comando

php artisan mad:api-token {login} {unit}
Argumento / flag Descricao
login Login do usuario dono do token (obrigatorio).
unit Id da unit — escopo fixo do token; obrigatorio ao emitir.
--name= Label do token (ex.: "integracao ERP X").
--abilities= CSV de permissoes finas ("orders.read,reports.*"); vazio = acesso total.
--days= TTL em dias; default config('mad.api.token_ttl_days'). 0 = sem expiracao.
--list Lista os tokens ativos do usuario (metadados, sem hash).
--revoke= Revoga (soft) um token do usuario por id.
php artisan mad:api-token admin 1 \
  --name="integracao ERP X" \
  --abilities="orders.read,orders.write" \
  --days=90
# imprime o claro UMA vez: mad_api_3f9c...

php artisan mad:api-token admin --list
php artisan mad:api-token admin --revoke=7

O TTL default vem de config('mad.api.token_ttl_days') — MAD_API_TOKEN_TTL_DAYS, 365 dias de fabrica. 0 emite token sem expiracao.

Emitindo pela tela admin

A tela Tokens de API vive em /app/tokens-api (ApiTokenList, control-plane iam: o admin ve todos os tokens do install). O botao de emissao abre o drawer ApiTokenForm, que delega ao mesmo MadApiTokenService::issue() — as validacoes fail-closed sao unicas, nao ha caminho alternativo.

Duas coisas dessa tela merecem atencao:

  • Reveal unico. O token em claro aparece uma so vez, num painel com botao de copiar. Fechou o drawer, o claro se perdeu de proposito — nao ha "mostrar de novo".
  • Checklist de abilities descoberta das rotas. As opcoes nao sao digitadas a mao: vem de MadApiTokenService::declaredAbilities(), que varre a route table extraindo os parametros de mad.api:<a>,<b> e mostra, ao lado de cada ability, as rotas que a exigem (GET /api/orders). Ability que nenhuma rota declara nao aparece para ser concedida.

A revogacao na listagem e soft (active = '0' + revoked_at): a linha fica para auditoria e o acesso cai na hora, porque o middleware revalida o token a cada requisicao.

Abilities

A rota declara o que exige; o token declara o que cobre. A checagem e MadApiTokenService::tokenCan(), fail-closed no miss:

Abilities do token Semantica
null ou [] Acesso total — passa em qualquer exigencia.
['*'] Acesso total.
['orders.read'] Match exato.
['orders.*'] Wildcard de prefixo: cobre orders.read, orders.x.y.
Qualquer outra 403, citando a ability que faltou.

Na rota, virgula significa todas exigidas:

Route::middleware('mad.api:orders.read')->get('/orders', ...);
Route::middleware('mad.api:orders.write,orders.approve')->post('/orders/{order}/approve', ...);

Formato aceito na emissao (normalizeAbilities(): trim + lowercase + dedup): segmentos [a-z0-9_-] separados por ponto, com .* opcional no fim — orders.read, reports.* — ou * sozinho. Formato invalido lanca RuntimeException na emissao, nao passa silenciosamente.

Falha de ability nao conta como falha de auth no rate-limit: o token e legitimo, so nao cobre aquela rota.

Ciclo de vida e revalidacao

resolve() so devolve token active = '1' e nao vencido. Alem disso, o middleware revalida o vinculo inteiro a cada requisicao — user ativo, unit ativa, unit pertencente ao usuario, tenant ativo. Desativar o usuario no IAM ou tirar a unit dele corta o token imediatamente, sem precisar revoga-lo.

Erros do lado do token, todos JSON:

Status Quando
401 Sem Bearer, ou token inexistente / expirado / revogado. Header WWW-Authenticate: Bearer.
403 Escopo invalido na revalidacao, ou token sem a ability exigida pela rota.
429 20 falhas de auth por IP em 600s (ApiRateLimiter); Retry-After: 600.

Boas praticas

  • Um token por integracao, com --name descritivo: revogar uma integracao nao pode derrubar as outras.
  • Abilities minimas. Token sem abilities e acesso total; e conveniente para um smoke local e ruim para producao.
  • TTL curto para o que e temporario. --days=7 para uma carga pontual; 0 (sem expiracao) so quando ha rotina de revogacao.
  • Nomes de ability sao contrato. Renomear a ability na rota invalida os tokens ja emitidos que a listavam — trate como versao de API.

Proximos passos