omnichannel-backend/docs/auth.md
Rafael Lopes 312f330bdf
All checks were successful
Deploy Dev / deploy (push) Successful in 3s
DOCS: Adicionado documentação sobre controle de acesso
2026-05-14 17:45:00 -03:00

269 lines
8.6 KiB
Markdown

# Módulo de Autenticação
## Visão geral
O módulo `auth` centraliza toda a lógica de autenticação do Omnichannel. Ele suporta múltiplos provedores de identidade e emite JWT próprio da aplicação, independente de qual provedor foi usado.
Provedores implementados:
- **LDAP / Active Directory** — login com usuário e senha do AD corporativo
- **Microsoft OAuth (Entra ID)** — login via conta Microsoft com redirect OAuth 2.0
A arquitetura foi desenhada para facilitar a adição de novos provedores no futuro.
---
## Estrutura de arquivos
```
src/modules/auth/
├── auth.module.ts # Registro do módulo no NestJS
├── auth.controller.ts # Rotas HTTP
├── auth.service.ts # Fachada — delega para os providers
├── auth.config.ts # Leitura de variáveis de ambiente
├── auth-token.service.ts # Emissão de JWT da aplicação
├── auth.types.ts # Interfaces TypeScript compartilhadas
└── providers/
├── ldap-auth.provider.ts # Autenticação LDAP/AD
├── microsoft-oauth.provider.ts # Autenticação Microsoft OAuth
└── oauth-state.service.ts # Proteção CSRF para OAuth
```
---
## Rotas disponíveis
| Método | Rota | Descrição |
|--------|---------------------------------|------------------------------------------------|
| GET | `/auth/config` | Retorna quais provedores estão habilitados |
| POST | `/auth/login` | Login com usuário e senha (LDAP/AD) |
| GET | `/auth/oauth/microsoft/start` | Inicia o fluxo OAuth com a Microsoft |
| GET | `/auth/oauth/microsoft/callback`| Callback que a Microsoft chama após o login |
---
## Variáveis de ambiente
```env
# Servidor
PORT=3001
FRONTEND_URL=http://localhost:3000
# JWT
JWT_SECRET=uma-chave-longa-e-aleatoria
JWT_EXPIRES_IN=8h
# Provedores ativos (separados por vírgula)
AUTH_PROVIDERS=ldap
# LDAP / Active Directory
LDAP_ENABLED=true
LDAP_URL=ldaps://servidor-ad:636
LDAP_DOMAIN=empresa.com.br
LDAP_USER_DN_TEMPLATE={{username}}@empresa.com.br
LDAP_SEARCH_BASE=DC=empresa,DC=com
LDAP_SEARCH_FILTER=(&(objectClass=user)(sAMAccountName={{username}}))
LDAP_TIMEOUT_MS=5000
# Microsoft Entra ID (desabilitado por padrão)
MICROSOFT_ENABLED=false
MICROSOFT_TENANT_ID=common
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_REDIRECT_URI=http://localhost:3001/auth/oauth/microsoft/callback
MICROSOFT_SUCCESS_REDIRECT_URL=http://localhost:3000/login
```
> `JWT_SECRET` deve ser uma string longa e aleatória. Em produção, nunca use o valor padrão do `.env.development`.
---
## Fluxo LDAP / Active Directory
```
Frontend
→ POST /auth/login { username, password }
→ AuthController
→ AuthService.loginWithLdap()
→ LdapAuthProvider.authenticate()
→ Conecta no servidor AD (LDAP_URL)
→ Faz bind com o usuário e senha
→ Se o bind falhar: UnauthorizedException
→ Busca dados do usuário no diretório (se LDAP_SEARCH_BASE configurado)
→ Monta objeto AuthenticatedUser
→ AuthTokenService.issueToken()
→ Gera JWT assinado com JWT_SECRET
→ Retorna { token, user } para o frontend
```
O AD apenas valida a identidade. O JWT emitido é da aplicação, não do AD.
---
## Fluxo Microsoft OAuth
```
1. Frontend redireciona para GET /auth/oauth/microsoft/start
→ Backend gera um state assinado (proteção CSRF)
→ Backend redireciona para login.microsoftonline.com
2. Usuário autentica na Microsoft
3. Microsoft chama GET /auth/oauth/microsoft/callback?code=...&state=...
→ Backend valida o state (assinatura + expiração)
→ Backend troca o code por access_token (chamada server-to-server)
→ Backend consulta Microsoft Graph /me para obter dados do usuário
→ AuthTokenService.issueToken() gera JWT próprio
→ Backend redireciona para MICROSOFT_SUCCESS_REDIRECT_URL?token=...
4. Frontend salva o token e navega para /home
```
---
## Proteção CSRF com OAuth State
O `OAuthStateService` protege o fluxo OAuth contra ataques de CSRF.
**Como funciona:**
1. No início do fluxo, o backend cria um state:
- Gera um nonce aleatório + timestamp
- Converte para base64url
- Assina com HMAC-SHA256 usando o `JWT_SECRET`
- Formato final: `payload.assinatura`
2. No callback, o backend verifica:
- O state tem os dois pedaços (`payload.assinatura`)
- A assinatura é válida (recalcula e compara com `timingSafeEqual`)
- O state não expirou (padrão: 10 minutos, configurável via `MICROSOFT_STATE_MAX_AGE_MS`)
Se qualquer verificação falhar, o callback é rejeitado com `400 Bad Request`.
---
## JWT da aplicação
Após qualquer autenticação bem-sucedida, o `AuthTokenService` emite um JWT com o seguinte payload:
```json
{
"sub": "identificador-do-usuario",
"name": "Nome Completo",
"email": "usuario@empresa.com",
"provider": "ldap",
"username": "usuario"
}
```
O `sub` é atualmente o email ou identificador externo. Quando houver banco de dados, deve ser substituído pelo ID interno da tabela `users`.
---
## Como adicionar um novo provedor
1. Crie o arquivo em `src/modules/auth/providers/novo-provedor.provider.ts`:
```typescript
import { Injectable } from '@nestjs/common';
import { AuthConfigService } from '../auth.config';
import { AuthTokenService } from '../auth-token.service';
import { AuthResult } from '../auth.types';
@Injectable()
export class NovoProvedorProvider {
constructor(
private readonly authConfig: AuthConfigService,
private readonly authToken: AuthTokenService,
) {}
async authenticate(/* dados necessários */): Promise<AuthResult> {
// 1. Valide as credenciais no provedor externo
// 2. Monte o objeto AuthenticatedUser
// 3. Emita o token com this.authToken.issueToken(user)
// 4. Retorne { token, user }
}
}
```
2. Registre o provider em `auth.module.ts`:
```typescript
providers: [
AuthConfigService,
AuthService,
AuthTokenService,
LdapAuthProvider,
MicrosoftOAuthProvider,
OAuthStateService,
NovoProvedorProvider, // adicione aqui
],
```
3. Injete no `AuthService` e exponha o método necessário:
```typescript
constructor(
private readonly authConfig: AuthConfigService,
private readonly ldapAuthProvider: LdapAuthProvider,
private readonly microsoftOAuthProvider: MicrosoftOAuthProvider,
private readonly novoProvedorProvider: NovoProvedorProvider, // injete aqui
) {}
loginComNovoProvedor(dados: any) {
return this.novoProvedorProvider.authenticate(dados);
}
```
4. Adicione a rota no `AuthController`.
5. Se o provedor precisar de configuração, adicione as variáveis no `AuthConfigService` e no `.env`.
---
## Diagnóstico de problemas
### Login LDAP falha com `UnauthorizedException`
- Verifique se `LDAP_URL` está acessível a partir do servidor backend
- Confirme que `LDAP_DOMAIN` ou `LDAP_USER_DN_TEMPLATE` está correto
- Teste a conectividade: `ldapsearch -H ldaps://servidor:636 -x`
- Verifique `LDAP_TIMEOUT_MS` — servidores lentos podem estar expirando
- O erro é genérico intencionalmente para não vazar informações. Adicione um `console.log(_error)` temporário no `catch` do `ldap-auth.provider.ts` para ver o erro real
### Login Microsoft falha com `400 Bad Request`
- O state expirou (padrão: 10 minutos). Se o usuário demorou muito na tela da Microsoft, repita o fluxo
- Verifique se `MICROSOFT_REDIRECT_URI` no `.env` é idêntico ao cadastrado no Azure App Registration
- Confirme que `MICROSOFT_CLIENT_ID` e `MICROSOFT_CLIENT_SECRET` estão corretos e não expiraram
### Token inválido no frontend
- Verifique se `JWT_SECRET` não mudou entre deploys — isso invalida todos os tokens emitidos anteriormente
- Confirme que o frontend está enviando o header `Authorization: Bearer <token>`
### `GET /auth/config` retorna os provedores errados
- Verifique `LDAP_ENABLED` e `MICROSOFT_ENABLED` no `.env`
- Reinicie o servidor — variáveis de ambiente são lidas na inicialização
---
## O que ainda falta para produção
- [ ] Tabela `users` no banco de dados
- [ ] Tabela `auth_identities` para vincular provedores externos ao usuário interno
- [ ] `sub` do JWT usando ID interno do banco, não email externo
- [ ] Guard NestJS para proteger rotas privadas (`@UseGuards(AuthGuard)`)
- [ ] Roles e permissões
- [ ] Auditoria de login
- [ ] Trocar token na query string por cookie HTTP-only (reduz exposição no browser)
---
## Documentacao complementar
A sincronizacao de usuarios autenticados com o banco, os perfis de acesso, as areas operacionais e os endpoints administrativos estao documentados em:
- [`access-control.md`](./access-control.md)