Adicionar Auth
commit
f6aeb9a658
260
Auth.md
Normal file
260
Auth.md
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
# 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)
|
||||||
Loading…
Reference in New Issue
Block a user