🔐 Autenticacao e Seguranca
Proteja seus dashboards enterprise com autenticacao robusta, controle de acesso granular e auditoria completa de acoes.
🔑 OAuth2 e OpenID Connect
Fluxos de autenticacao modernos para aplicacoes enterprise
📋 O que e OAuth2/OIDC
OAuth2 e um protocolo de autorizacao que permite que aplicacoes obtenham acesso limitado a contas de usuarios sem expor credenciais. OpenID Connect (OIDC) e uma camada de identidade sobre OAuth2 que adiciona autenticacao, permitindo verificar a identidade do usuario.
- • Autoriza acesso a recursos
- • Access Token para APIs
- • Refresh Token para renovacao
- • Scopes definem permissoes
- • Autentica identidade do usuario
- • ID Token com claims do usuario
- • UserInfo endpoint
- • Discovery e JWKS endpoints
🔄 Fluxos de Autorizacao
Mais seguro para SPAs e apps mobile. PKCE previne ataques de interceptacao.
2. Usuario autoriza no IdP
3. IdP retorna authorization_code
4. App troca code + verifier por tokens
Para comunicacao machine-to-machine (M2M). Nao envolve usuario.
Para dispositivos sem browser (TVs, CLIs, IoT).
Inseguro - tokens expostos na URL. Use Authorization Code + PKCE.
💻 Codigo: Implementacao PKCE
// Geracao PKCE
function generatePKCE() {
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto
.createHash('sha256')
.update(verifier)
.digest('base64url');
return { verifier, challenge };
}
// Inicio do fluxo
const { verifier, challenge } = generatePKCE();
sessionStorage.setItem('pkce_verifier', verifier);
const authUrl = new URL('https://auth.empresa.com/authorize');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('code_challenge', challenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('state', crypto.randomUUID());
window.location.href = authUrl.toString();
// Callback - troca do code por tokens
async function handleCallback(code: string) {
const verifier = sessionStorage.getItem('pkce_verifier');
const response = await fetch('https://auth.empresa.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
code,
code_verifier: verifier!,
}),
});
const { access_token, id_token, refresh_token } = await response.json();
// Armazenar tokens de forma segura
}
🏢 Identity Providers Populares
🎫 JWT e Gestao de Sessions
Tokens seguros e gerenciamento de sessoes de usuario
📋 Anatomia do JWT
JSON Web Token (JWT) e um formato compacto e seguro para transmitir informacoes entre partes. Consiste em tres partes separadas por pontos: Header, Payload e Signature.
⚖️ JWT vs Sessions Tradicionais
- ✓Escalavel horizontalmente
- ✓Sem estado no servidor
- ✓Funciona entre dominios
- ✗Dificil invalidar antes do expiry
- ✗Payload visivel (nao criptografado)
- ✓Revogacao imediata
- ✓Dados sensiveis no servidor
- ✓Controle total de sessoes
- ✗Requer storage centralizado
- ✗Mais complexo escalar
💻 Codigo: Token Refresh Automatico
// Token Manager com Refresh Automatico
class TokenManager {
private accessToken: string | null = null;
private refreshToken: string | null = null;
private refreshPromise: Promise<void> | null = null;
async getAccessToken(): Promise<string> {
if (this.isTokenValid()) {
return this.accessToken!;
}
// Evita multiplas chamadas de refresh simultaneas
if (!this.refreshPromise) {
this.refreshPromise = this.refreshAccessToken();
}
await this.refreshPromise;
this.refreshPromise = null;
return this.accessToken!;
}
private isTokenValid(): boolean {
if (!this.accessToken) return false;
const payload = JSON.parse(atob(this.accessToken.split('.')[1]));
const expiresAt = payload.exp * 1000;
const bufferMs = 60000; // 1 min de buffer
return Date.now() < expiresAt - bufferMs;
}
private async refreshAccessToken(): Promise<void> {
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: this.refreshToken }),
});
if (!response.ok) {
this.logout();
throw new Error('Session expired');
}
const { access_token, refresh_token } = await response.json();
this.accessToken = access_token;
this.refreshToken = refresh_token;
}
}
// Axios Interceptor para refresh automatico
axios.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401 && !error.config._retry) {
error.config._retry = true;
const token = await tokenManager.getAccessToken();
error.config.headers.Authorization = `Bearer ${token}`;
return axios(error.config);
}
return Promise.reject(error);
}
);
💡 Dica de Seguranca
Nunca armazene JWTs no localStorage para aplicacoes sensiveis - use httpOnly cookies para o refresh token e mantenha o access token apenas em memoria. Isso protege contra ataques XSS.
🏢 SSO Empresarial
Single Sign-On com Active Directory, SAML e integracao corporativa
📋 O que e SSO
Single Sign-On permite que usuarios autentiquem uma vez e acessem multiplos sistemas sem login repetido. Em ambientes corporativos, geralmente integra com Active Directory (AD) ou outros Identity Providers via protocolos como SAML, OIDC ou LDAP.
📊 SAML vs OpenID Connect
| Aspecto | SAML 2.0 | OIDC |
|---|---|---|
| Formato | XML (verboso) | JSON (compacto) |
| Transporte | HTTP POST/Redirect | REST APIs |
| Ideal para | Enterprise legado | Apps modernas, mobile |
| Complexidade | Alta | Media |
| Suporte | Corporacoes tradicionais | Providers modernos |
🔄 Fluxo SAML SP-Initiated
💻 Codigo: Integracao Azure AD (MSAL)
// Configuracao MSAL para Azure AD
import { PublicClientApplication, Configuration } from '@azure/msal-browser';
const msalConfig: Configuration = {
auth: {
clientId: 'SEU_CLIENT_ID',
authority: 'https://login.microsoftonline.com/SEU_TENANT_ID',
redirectUri: 'https://dashboard.empresa.com/callback',
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: false,
},
};
const msalInstance = new PublicClientApplication(msalConfig);
// Scopes para Graph API
const loginRequest = {
scopes: ['User.Read', 'GroupMember.Read.All'],
};
// Login
async function signIn() {
try {
const response = await msalInstance.loginPopup(loginRequest);
console.log('Usuario:', response.account?.username);
// Obter token para API
const tokenResponse = await msalInstance.acquireTokenSilent({
scopes: ['api://SEU_API_ID/.default'],
account: response.account!,
});
return tokenResponse.accessToken;
} catch (error) {
console.error('Erro de login:', error);
throw error;
}
}
// Obter grupos do usuario para RBAC
async function getUserGroups(token: string) {
const response = await fetch('https://graph.microsoft.com/v1.0/me/memberOf', {
headers: { Authorization: `Bearer ${token}` },
});
const data = await response.json();
return data.value
.filter((item: any) => item['@odata.type'] === '#microsoft.graph.group')
.map((group: any) => group.displayName);
}
👥 RBAC - Role-Based Access Control
Controle de acesso granular baseado em papeis e permissoes
📋 Modelo RBAC
Role-Based Access Control atribui permissoes a roles (papeis), e usuarios recebem roles. Isso simplifica a gestao de acesso - ao inves de gerenciar permissoes individuais por usuario, voce gerencia roles.
🏛️ Exemplo: Hierarquia de Roles para Dashboard
Acesso total ao sistema
Gerencia usuarios, configs, dashboards
Cria dashboards, ve todos os dados
Ve dashboards do seu departamento
Apenas visualiza dashboards compartilhados
💻 Codigo: Sistema RBAC Completo
// Definicao de permissoes
type Permission =
| 'dashboard:create' | 'dashboard:read' | 'dashboard:update' | 'dashboard:delete'
| 'data:read:all' | 'data:read:dept' | 'data:export'
| 'users:read' | 'users:manage'
| 'config:read' | 'config:update';
type Role = 'super_admin' | 'admin' | 'manager' | 'analyst' | 'viewer';
// Matriz de permissoes por role
const rolePermissions: Record<Role, Permission[]> = {
super_admin: ['*'] as any, // Todas as permissoes
admin: [
'dashboard:create', 'dashboard:read', 'dashboard:update', 'dashboard:delete',
'data:read:all', 'data:export',
'users:read', 'users:manage',
'config:read', 'config:update',
],
manager: [
'dashboard:create', 'dashboard:read', 'dashboard:update',
'data:read:all', 'data:export',
'users:read',
],
analyst: [
'dashboard:read',
'data:read:dept',
],
viewer: [
'dashboard:read',
],
};
// Verificador de permissao
class PermissionChecker {
constructor(private user: { roles: Role[]; department?: string }) {}
hasPermission(permission: Permission): boolean {
return this.user.roles.some(role => {
const perms = rolePermissions[role];
return perms.includes('*' as any) || perms.includes(permission);
});
}
canAccessDepartment(deptId: string): boolean {
if (this.hasPermission('data:read:all')) return true;
return this.user.department === deptId;
}
canAccessDashboard(dashboard: { ownerId: string; shared: boolean; deptId: string }): boolean {
if (this.hasPermission('dashboard:read') && this.hasPermission('data:read:all')) {
return true;
}
if (dashboard.shared) return true;
return this.canAccessDepartment(dashboard.deptId);
}
}
// Middleware Express
const requirePermission = (permission: Permission) => {
return (req: Request, res: Response, next: NextFunction) => {
const checker = new PermissionChecker(req.user);
if (!checker.hasPermission(permission)) {
return res.status(403).json({
error: 'Forbidden',
required: permission,
});
}
next();
};
};
// Uso
app.post('/api/dashboards',
requirePermission('dashboard:create'),
createDashboardHandler
);
✅ Fazer vs ❌ Evitar
- • Principio do menor privilegio
- • Revisar permissoes periodicamente
- • Usar grupos/roles, nao permissoes diretas
- • Implementar heranca de roles
- • Logar todas as mudancas de acesso
- • Permissoes hardcoded no codigo
- • Verificacao apenas no frontend
- • Roles muito amplas (God mode)
- • Ignorar separacao de ambientes
- • Nao ter processo de offboarding
🛡️ Protecao de Rotas
Guards, middlewares e protecao em multiplas camadas
📋 Defesa em Profundidade
Protecao de rotas deve acontecer em multiplas camadas - frontend, API gateway, backend e banco de dados. Nunca confie apenas na verificacao do frontend.
💻 Codigo: React Route Guards
// ProtectedRoute component
interface ProtectedRouteProps {
children: React.ReactNode;
requiredPermissions?: Permission[];
requiredRoles?: Role[];
fallback?: React.ReactNode;
}
function ProtectedRoute({
children,
requiredPermissions = [],
requiredRoles = [],
fallback = <Navigate to="/unauthorized" />
}: ProtectedRouteProps) {
const { user, isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return <LoadingSpinner />;
}
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} />;
}
const hasRequiredRoles = requiredRoles.length === 0 ||
requiredRoles.some(role => user.roles.includes(role));
const hasRequiredPermissions = requiredPermissions.length === 0 ||
requiredPermissions.every(perm => user.permissions.includes(perm));
if (!hasRequiredRoles || !hasRequiredPermissions) {
return fallback;
}
return children;
}
// Uso nas rotas
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route element={<ProtectedRoute><MainLayout /></ProtectedRoute>}>
<Route path="/dashboard" element={<DashboardPage />} />
<Route
path="/admin"
element={
<ProtectedRoute requiredRoles={['admin', 'super_admin']}>
<AdminPage />
</ProtectedRoute>
}
/>
<Route
path="/reports/export"
element={
<ProtectedRoute requiredPermissions={['data:export']}>
<ExportPage />
</ProtectedRoute>
}
/>
</Route>
<Route path="/unauthorized" element={<UnauthorizedPage />} />
</Routes>
💻 Codigo: Backend Middleware Chain
// Middleware de autenticacao
const authenticate = async (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Token required' });
}
try {
const token = authHeader.split(' ')[1];
const payload = await verifyJWT(token);
req.user = {
id: payload.sub,
email: payload.email,
roles: payload.roles,
permissions: await getPermissionsForRoles(payload.roles),
};
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
};
// Middleware de rate limiting por usuario
const rateLimitByUser = rateLimit({
windowMs: 60 * 1000, // 1 minuto
max: 100,
keyGenerator: (req) => req.user?.id || req.ip,
handler: (req, res) => {
res.status(429).json({ error: 'Too many requests' });
},
});
// Middleware de audit logging
const auditLog = (action: string) => {
return (req: Request, res: Response, next: NextFunction) => {
const originalSend = res.send;
res.send = function(body) {
logger.info({
action,
userId: req.user?.id,
method: req.method,
path: req.path,
statusCode: res.statusCode,
ip: req.ip,
userAgent: req.headers['user-agent'],
timestamp: new Date().toISOString(),
});
return originalSend.call(this, body);
};
next();
};
};
// Aplicacao em cascata
app.use('/api', authenticate);
app.use('/api', rateLimitByUser);
app.use('/api/admin', requireRole('admin'));
app.use('/api/admin', auditLog('admin_action'));
🗄️ Row Level Security (PostgreSQL)
-- Habilitar RLS na tabela
ALTER TABLE dashboards ENABLE ROW LEVEL SECURITY;
-- Policy: usuarios veem apenas dashboards do seu departamento
CREATE POLICY department_isolation ON dashboards
FOR SELECT
USING (
department_id = current_setting('app.current_department')::uuid
OR shared = true
OR created_by = current_setting('app.current_user')::uuid
);
-- Policy: apenas admins podem deletar
CREATE POLICY admin_delete ON dashboards
FOR DELETE
USING (
current_setting('app.current_role') IN ('admin', 'super_admin')
);
-- Setar contexto na conexao (via aplicacao)
-- No backend, antes de cada query:
SET LOCAL app.current_user = 'user-uuid-here';
SET LOCAL app.current_department = 'dept-uuid-here';
SET LOCAL app.current_role = 'analyst';
📜 Audit Logs e Rastreabilidade
Registre todas as acoes para compliance, seguranca e debugging
📋 Por que Audit Logs
Audit logs sao registros imutaveis de todas as acoes significativas no sistema. Sao essenciais para compliance (LGPD, SOX, HIPAA), investigacao de incidentes de seguranca, e debugging de problemas em producao.
- • Detectar acessos suspeitos
- • Identificar vazamentos
- • Forense de incidentes
- • LGPD - quem acessou dados
- • SOX - controles financeiros
- • ISO 27001 - auditoria
- • Debug de problemas
- • Analise de uso
- • Rollback de acoes
📝 O que Registrar
💻 Codigo: Sistema de Audit Log
// Schema do Audit Log
interface AuditLog {
id: string;
timestamp: Date;
// Quem
userId: string;
userEmail: string;
userRoles: string[];
// O que
action: AuditAction;
resourceType: string;
resourceId: string;
// Como
method: string;
path: string;
statusCode: number;
// Contexto
ip: string;
userAgent: string;
sessionId: string;
// Detalhes
changes?: {
before: Record<string, any>;
after: Record<string, any>;
};
metadata?: Record<string, any>;
}
type AuditAction =
| 'LOGIN' | 'LOGOUT' | 'LOGIN_FAILED'
| 'CREATE' | 'READ' | 'UPDATE' | 'DELETE'
| 'EXPORT' | 'SHARE' | 'PERMISSION_CHANGE'
| 'CONFIG_CHANGE' | 'BULK_OPERATION';
// Servico de Audit
class AuditService {
private queue: AuditLog[] = [];
private batchSize = 100;
private flushInterval = 5000; // 5s
constructor(private storage: AuditStorage) {
setInterval(() => this.flush(), this.flushInterval);
}
log(entry: Omit<AuditLog, 'id' | 'timestamp'>) {
this.queue.push({
...entry,
id: crypto.randomUUID(),
timestamp: new Date(),
});
if (this.queue.length >= this.batchSize) {
this.flush();
}
}
private async flush() {
if (this.queue.length === 0) return;
const batch = this.queue.splice(0, this.batchSize);
await this.storage.writeBatch(batch);
}
// Helper para detectar mudancas
static diff(before: any, after: any): { before: any; after: any } {
const changes = { before: {}, after: {} };
const allKeys = new Set([...Object.keys(before || {}), ...Object.keys(after || {})]);
for (const key of allKeys) {
if (JSON.stringify(before?.[key]) !== JSON.stringify(after?.[key])) {
changes.before[key] = before?.[key];
changes.after[key] = after?.[key];
}
}
return changes;
}
}
// Uso com decorators
function Audited(action: AuditAction, resourceType: string) {
return function(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = async function(...args: any[]) {
const before = await this.getResource?.(args);
const result = await original.apply(this, args);
const after = await this.getResource?.(args);
auditService.log({
action,
resourceType,
resourceId: result?.id || args[0],
changes: AuditService.diff(before, after),
...getCurrentContext(), // userId, ip, etc
});
return result;
};
return descriptor;
};
}
// Uso
class DashboardService {
@Audited('UPDATE', 'dashboard')
async updateDashboard(id: string, data: UpdateDashboardDTO) {
return this.repository.update(id, data);
}
}
📊 Dashboard de Auditoria
💡 Politica de Retencao
Defina politicas claras de retencao de logs considerando requisitos legais e custos de armazenamento:
- • Hot storage (30 dias): Elasticsearch/OpenSearch para busca rapida
- • Warm storage (1 ano): S3 Standard para acesso ocasional
- • Cold storage (7 anos): S3 Glacier para compliance