Pular para o conteúdo principal

Autenticação JWT

O Bemba autentica as chamadas à API com tokens JWT (LexikJWTAuthenticationBundle) e renova a sessão com tokens de renovação guardados na base (Gesdinet).

Tokens​

TokenDuraçãoOnde circula
Token de acesso (JWT assinado RSA)1 hora (token_ttl: 3600)cookie BEARER (HttpOnly); o cabeçalho Authorization: Bearer … também é aceite
Token de renovação30 diascookie REFRESH_TOKEN (HttpOnly, limitado ao caminho /api/token); tabela refresh_tokens

O cookie tem prioridade; o cabeçalho serve para scripts e ferramentas de linha de comandos. Em HTTPS, os cookies são Secure e SameSite=Strict.

Rotas​

RotaFunção
POST /api/loginInício de sessão (email, password); devolve o token, o token de renovação e o perfil, ou requires2FA se a autenticação de dois fatores estiver ativa para a conta
POST /api/2fa/verifySegunda etapa do início de sessão
POST /api/token/refreshNovo token de acesso; o token de renovação é substituído a cada uso
POST /api/token/invalidateFim de sessão: apaga o token de renovação e os cookies

O frontend renova o token antes de cada pedido quando não está em memória (recarregamento da página), expirou ou está perto de expirar, e também após uma resposta 401.

Proteção CSRF​

Quando o pedido é autenticado por cookie, qualquer mutação (POST, PUT, PATCH, DELETE) deve levar o cabeçalho X-CSRF-TOKEN, igual ao cookie XSRF-TOKEN (padrão « double submit cookie »). As chamadas autenticadas pelo cabeçalho Authorization não precisam dele. Desativável apenas em desenvolvimento (CSRF_ENABLED=false).

Chaves​

ElementoValor
Localização~/pch-sig/backend/config/jwt/ no servidor, montado em /app/config/jwt do contentor backend: só de leitura em produção, com escrita em pré-produção
Frase-passeJWT_PASSPHRASE em .env.prod
Criaçãoo entrypoint do backend sabe gerar um par RSA 4096 se faltarem ou se a frase-passe não as abrir, desde que a pasta seja gravável (pré-produção, desenvolvimento); em produção, as chaves criam-se no anfitrião
Rotação das chaves

A frase-passe de .env.prod e as chaves do anfitrião andam juntas. Para mudar uma, mudar a outra:

cd ~/pch-sig/backend/config/jwt
openssl genpkey -out private.pem -aes256 -algorithm rsa -pkeyopt rsa_keygen_bits:4096 -pass env:JWT_PASSPHRASE
openssl pkey -in private.pem -out public.pem -pubout -passin env:JWT_PASSPHRASE
sudo chown 82:82 private.pem public.pem # 82 = www-data na imagem php:8.3-fpm-alpine
sudo chmod 600 private.pem && sudo chmod 644 public.pem

(JWT_PASSPHRASE exportada na shell durante o comando e depois copiada para .env.prod.) Recriar a seguir o contentor backend (up -d --force-recreate --no-deps backend). Os utilizadores não perdem a sessão: o token de renovação não depende da chave, a primeira chamada responde 401, o frontend renova e repete o pedido. Procedimento completo de rotação dos segredos: docs/ROTATION_SECRETS.md do repositório.

Obter um token em linha de comandos​

curl -s -X POST https://sig-preprod.ucp-pch.org/api/login \
-H "Content-Type: application/json" \
-d '{"email":"<EMAIL>","password":"<PALAVRA_PASSE>"}' > token.json

curl -s https://sig-preprod.ucp-pch.org/api/users/me/permissions \
-H "Authorization: Bearer $(jq -r .token token.json)"

Nunca colar um token nem uma palavra-passe num pedido de suporte, num registo ou numa conversa; apagar token.json depois de usar.

Resolução de problemas​

SintomaCausaAção
500 no início de sessão, « Unable to create a signed JWT »chave privada ilegível por www-data, ou frase-passe que não corresponde às chavesno anfitrião, proprietário 82:82 e modo 600 para private.pem; senão verificar a frase-passe
Todos os utilizadores perderam a sessãotabela refresh_tokens esvaziada, ou cookies apagadosvoltar a iniciar sessão
401 em ciclorelógio do servidor desfasado, ou cookie bloqueado pelo navegadordocker exec pch_backend date; verificar que o site é servido em HTTPS
403 « CSRF » numa mutaçãocabeçalho X-CSRF-TOKEN ausente ou expiradorecarregar a página (o cookie XSRF-TOKEN é reemitido)
docker exec -u www-data pch_backend php bin/console debug:config lexik_jwt_authentication

Ver também​