Table of Contents
- Módulo de Autenticação
- Visão geral
- Estrutura de arquivos
- Rotas disponíveis
- Variáveis de ambiente
- Fluxo LDAP / Active Directory
- Fluxo Microsoft OAuth
- Proteção CSRF com OAuth State
- JWT da aplicação
- Sincronização de usuário e acesso
- Como adicionar um novo provedor
- Diagnóstico de problemas
- Login LDAP falha com UnauthorizedException
- Login Microsoft falha com 400 Bad Request
- Token inválido no frontend
- GET /auth/config retorna os provedores errados
- O que ainda falta para produção
- Documentacao complementar
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
├── user-access.service.ts # Sincronização do usuário autenticado com o banco
├── 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
# 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_SECRETdeve 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
→ UserAccessService.syncAuthenticatedUser()
→ Cria/atualiza usuarios e usuarios_provedores
→ Carrega perfis, areas e area principal
→ 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
→ UserAccessService.syncAuthenticatedUser() sincroniza o usuário no banco
→ 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:
-
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
-
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)
- O state tem os dois pedaços (
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:
{
"sub": "4",
"name": "Nome Completo",
"email": "usuario@empresa.com",
"provider": "ldap",
"username": "usuario",
"perfis": ["Admin"],
"profiles": ["Admin"],
"areas": ["Suporte"],
"areaPrincipal": "Suporte",
"accessStatus": "assigned"
}
O sub usa o ID interno da tabela usuarios, convertido para string. O mesmo ID também é retornado no objeto de usuário como databaseId.
O JWT é emitido e salvo no frontend, mas ainda falta a camada de AuthGuard no NestJS para validar o token nas rotas privadas. Portanto, hoje o token representa a sessão do usuário para o frontend, mas o backend ainda precisa ser endurecido para produção.
Sincronização de usuário e acesso
Após o provedor autenticar o usuário, o UserAccessService:
- faz upsert em
usuariosusando email ou fallbackprovider:username; - faz upsert em
usuarios_provedores; - consulta perfis em
usuarios_perfis+perfis_acesso; - consulta especialidades em
usuarios_areas+areas; - retorna o usuário enriquecido com:
databaseId;perfis/profiles;areas;areaPrincipal;accessStatus.
accessStatus fica como assigned quando o usuário possui perfil e área. Usuários sem vínculo suficiente entram como unassigned e caem na tela de pendência no frontend.
Como adicionar um novo provedor
- Crie o arquivo em
src/modules/auth/providers/novo-provedor.provider.ts:
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 }
}
}
- Registre o provider em
auth.module.ts:
providers: [
AuthConfigService,
AuthService,
AuthTokenService,
LdapAuthProvider,
MicrosoftOAuthProvider,
OAuthStateService,
NovoProvedorProvider, // adicione aqui
],
- Injete no
AuthServicee exponha o método necessário:
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);
}
-
Adicione a rota no
AuthController. -
Se o provedor precisar de configuração, adicione as variáveis no
AuthConfigServicee no.env.
Diagnóstico de problemas
Login LDAP falha com UnauthorizedException
- Verifique se
LDAP_URLestá acessível a partir do servidor backend - Confirme que
LDAP_DOMAINouLDAP_USER_DN_TEMPLATEestá 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 nocatchdoldap-auth.provider.tspara 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_URIno.envé idêntico ao cadastrado no Azure App Registration - Confirme que
MICROSOFT_CLIENT_IDeMICROSOFT_CLIENT_SECRETestão corretos e não expiraram
Token inválido no frontend
- Verifique se
JWT_SECRETnã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_ENABLEDeMICROSOFT_ENABLEDno.env - Reinicie o servidor — variáveis de ambiente são lidas na inicialização
O que ainda falta para produção
- Tabela
usuariosno banco de dados - Tabela
usuarios_provedorespara vincular provedores externos ao usuário interno subdo JWT usando ID interno do banco- Guard NestJS para proteger rotas privadas (
@UseGuards(AuthGuard)) - Roles e permissões validadas no backend
- 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: