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
| Token | Duração | Onde 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ção | 30 dias | cookie 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
| Rota | Função |
|---|---|
POST /api/login | Iní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/verify | Segunda etapa do início de sessão |
POST /api/token/refresh | Novo token de acesso; o token de renovação é substituído a cada uso |
POST /api/token/invalidate | Fim 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
| Elemento | Valor |
|---|---|
| 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-passe | JWT_PASSPHRASE em .env.prod |
| Criação | o 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 |
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
| Sintoma | Causa | Açã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 chaves | no anfitrião, proprietário 82:82 e modo 600 para private.pem; senão verificar a frase-passe |
| Todos os utilizadores perderam a sessão | tabela refresh_tokens esvaziada, ou cookies apagados | voltar a iniciar sessão |
| 401 em ciclo | relógio do servidor desfasado, ou cookie bloqueado pelo navegador | docker exec pch_backend date; verificar que o site é servido em HTTPS |
| 403 « CSRF » numa mutação | cabeçalho X-CSRF-TOKEN ausente ou expirado | recarregar a página (o cookie XSRF-TOKEN é reemitido) |
docker exec -u www-data pch_backend php bin/console debug:config lexik_jwt_authentication