# 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 { // 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 ` ### `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)