2 Auth
Rafael Alves Lopes edited this page 2026-05-27 16:41:38 -03:00

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_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
  → 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:

  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:

{
  "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:

  1. faz upsert em usuarios usando email ou fallback provider:username;
  2. faz upsert em usuarios_provedores;
  3. consulta perfis em usuarios_perfis + perfis_acesso;
  4. consulta especialidades em usuarios_areas + areas;
  5. 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

  1. 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 }
  }
}
  1. Registre o provider em auth.module.ts:
providers: [
  AuthConfigService,
  AuthService,
  AuthTokenService,
  LdapAuthProvider,
  MicrosoftOAuthProvider,
  OAuthStateService,
  NovoProvedorProvider, // adicione aqui
],
  1. Injete no AuthService e 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);
}
  1. Adicione a rota no AuthController.

  2. 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 usuarios no banco de dados
  • Tabela usuarios_provedores para vincular provedores externos ao usuário interno
  • sub do 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: