| name | auth-implementation-patterns |
| description | Define padrões para implementação de autenticação e autorização (JWT, RBAC, sessões). Use ao implementar sistemas de login, proteger rotas ou resolver problemas de segurança e controle de acesso. |
Padrões de Implementação de Autenticação e Autorização
Construa sistemas de autenticação e autorização seguros e escaláveis usando padrões da indústria e melhores práticas modernas.
Quando Usar Esta Skill
- Implementar sistemas de autenticação de usuário
- Proteger APIs REST ou GraphQL
- Adicionar OAuth2/login social
- Implementar controle de acesso baseado em funções (Role-Based Access Control - RBAC)
- Projetar gerenciamento de sessão (session management)
- Migrar sistemas de autenticação
- Debugar problemas de autenticação
- Implementar SSO (Single Sign-On) ou multi-tenancy
Contexto Obrigatório do SegFlow CRM
- Backend em JavaScript: no SegFlow CRM o backend permanece em JavaScript com JSDoc +
checkJs; use exemplos TypeScript desta skill apenas como referência e converta para JS ao implementar.
- Arquitetura limpa: preserve o fluxo
routes -> controllers -> useCases -> repositories -> entities; não colocar regra de negócio em controller nem acesso direto ao banco em rotas.
- Fluxo de auth oficial do projeto: manter JWT de curta duração + refresh token com rotação automática, refresh token com hash no banco e cookies
httpOnly (segflow_token e segflow_refresh com path restrito em /api/auth).
- Validação e erros: validar entrada com Zod + middleware
validate; erros de negócio via AppError e subclasses (UnauthorizedError, ValidationError, etc.).
- Escopo de mudanças: não introduzir sessão com
express-session, OAuth2/passport ou RBAC novo sem requisito explícito do usuário e sem aderência ao stack atual do repositório.
Conceitos Principais (Core Concepts)
1. Autenticação vs Autorização
Autenticação (AuthN): Quem é você?
- Verificação de identidade (usuário/senha, OAuth, biometria)
- Emissão de credenciais (sessões, tokens)
- Gerenciamento de login/logout
Autorização (AuthZ): O que você pode fazer?
- Checagem de permissões
- Controle de acesso baseado em funções (RBAC)
- Validação de posse do recurso
- Imposição de políticas (Policy enforcement)
2. Estratégias de Autenticação
Baseada em Sessão (Session-Based):
- O servidor armazena o estado da sessão
- O ID da sessão fica no cookie
- Tradicional, simples, com estado (stateful)
Baseada em Token (JWT):
- Sem estado (stateless), autossuficiente
- Escala horizontalmente
- Pode armazenar "claims" (reivindicações)
OAuth2/OpenID Connect:
- Delegação de autenticação
- Login social (Google, GitHub)
- SSO Corporativo
Autenticação JWT
Padrão 1: Implementação de JWT
import jwt from "jsonwebtoken";
import { Request, Response, NextFunction } from "express";
interface JWTPayload {
userId: string;
email: string;
role: string;
iat: number;
exp: number;
}
function generateTokens(userId: string, email: string, role: string) {
const accessToken = jwt.sign(
{ userId, email, role },
process.env.JWT_SECRET!,
{ expiresIn: "15m" },
);
const refreshToken = jwt.sign(
{ userId },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: "7d" },
);
return { accessToken, refreshToken };
}
function verifyToken(token: string): JWTPayload {
try {
return jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new Error("Token expired");
}
if (error instanceof jwt.JsonWebTokenError) {
throw new Error("Invalid token");
}
throw error;
}
}
function authenticate(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ error: "No token provided" });
}
const token = authHeader.substring(7);
try {
const payload = verifyToken(token);
req.user = payload;
next();
} catch (error) {
return res.status(401).json({ error: "Invalid token" });
}
}
app.get("/api/profile", authenticate, (req, res) => {
res.json({ user: req.user });
});
Padrão 2: Fluxo do Refresh Token
interface StoredRefreshToken {
token: string;
userId: string;
expiresAt: Date;
createdAt: Date;
}
class RefreshTokenService {
async storeRefreshToken(userId: string, refreshToken: string) {
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
await db.refreshTokens.create({
token: await hash(refreshToken),
userId,
expiresAt,
});
}
async refreshAccessToken(refreshToken: string) {
let payload;
try {
payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as {
userId: string;
};
} catch {
throw new Error("Invalid refresh token");
}
const storedToken = await db.refreshTokens.findOne({
where: {
token: await hash(refreshToken),
userId: payload.userId,
expiresAt: { $gt: new Date() },
},
});
if (!storedToken) {
throw new Error("Refresh token not found or expired");
}
const user = await db.users.findById(payload.userId);
if (!user) {
throw new Error("User not found");
}
const accessToken = jwt.sign(
{ userId: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET!,
{ expiresIn: "15m" },
);
return { accessToken };
}
async revokeRefreshToken(refreshToken: string) {
await db.refreshTokens.deleteOne({
token: await hash(refreshToken),
});
}
async revokeAllUserTokens(userId: string) {
await db.refreshTokens.deleteMany({ userId });
}
}
app.post("/api/auth/refresh", async (req, res) => {
const { refreshToken } = req.body;
try {
const { accessToken } =
await refreshTokenService.refreshAccessToken(refreshToken);
res.json({ accessToken });
} catch (error) {
res.status(401).json({ error: "Invalid refresh token" });
}
});
app.post("/api/auth/logout", authenticate, async (req, res) => {
const { refreshToken } = req.body;
await refreshTokenService.revokeRefreshToken(refreshToken);
res.json({ message: "Logged out successfully" });
});
Autenticação Baseada em Sessão
Padrão 1: Express Session
import session from "express-session";
import RedisStore from "connect-redis";
import { createClient } from "redis";
const redisClient = createClient({
url: process.env.REDIS_URL,
});
await redisClient.connect();
app.use(
session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === "production",
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000,
sameSite: "strict",
},
}),
);
app.post("/api/auth/login", async (req, res) => {
const { email, password } = req.body;
const user = await db.users.findOne({ email });
if (!user || !(await verifyPassword(password, user.passwordHash))) {
return res.status(401).json({ error: "Invalid credentials" });
}
req.session.userId = user.id;
req.session.role = user.role;
res.json({ user: { id: user.id, email: user.email, role: user.role } });
});
function requireAuth(req: Request, res: Response, next: NextFunction) {
if (!req.session.userId) {
return res.status(401).json({ error: "Not authenticated" });
}
next();
}
app.get("/api/profile", requireAuth, async (req, res) => {
const user = await db.users.findById(req.session.userId);
res.json({ user });
});
app.post("/api/auth/logout", (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: "Logout failed" });
}
res.clearCookie("connect.sid");
res.json({ message: "Logged out successfully" });
});
});
OAuth2 / Login Social
Padrão 1: OAuth2 com Passport.js
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { Strategy as GitHubStrategy } from "passport-github2";
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: "/api/auth/google/callback",
},
async (accessToken, refreshToken, profile, done) => {
try {
let user = await db.users.findOne({
googleId: profile.id,
});
if (!user) {
user = await db.users.create({
googleId: profile.id,
email: profile.emails?.[0]?.value,
name: profile.displayName,
avatar: profile.photos?.[0]?.value,
});
}
return done(null, user);
} catch (error) {
return done(error, undefined);
}
},
),
);
app.get(
"/api/auth/google",
passport.authenticate("google", {
scope: ["profile", "email"],
}),
);
app.get(
"/api/auth/google/callback",
passport.authenticate("google", { session: false }),
(req, res) => {
const tokens = generateTokens(req.user.id, req.user.email, req.user.role);
res.redirect(
`${process.env.FRONTEND_URL}/auth/callback?token=${tokens.accessToken}`,
);
},
);
Padrões de Autorização
Padrão 1: Role-Based Access Control (RBAC)
enum Role {
USER = "user",
MODERATOR = "moderator",
ADMIN = "admin",
}
const roleHierarchy: Record<Role, Role[]> = {
[Role.ADMIN]: [Role.ADMIN, Role.MODERATOR, Role.USER],
[Role.MODERATOR]: [Role.MODERATOR, Role.USER],
[Role.USER]: [Role.USER],
};
function hasRole(userRole: Role, requiredRole: Role): boolean {
return roleHierarchy[userRole].includes(requiredRole);
}
function requireRole(...roles: Role[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: "Not authenticated" });
}
if (!roles.some((role) => hasRole(req.user.role, role))) {
return res.status(403).json({ error: "Insufficient permissions" });
}
next();
};
}
app.delete(
"/api/users/:id",
authenticate,
requireRole(Role.ADMIN),
async (req, res) => {
await db.users.delete(req.params.id);
res.json({ message: "User deleted" });
},
);
Padrão 2: Controle de Acesso Baseado em Permissão (Permission-Based)
enum Permission {
READ_USERS = "read:users",
WRITE_USERS = "write:users",
DELETE_USERS = "delete:users",
READ_POSTS = "read:posts",
WRITE_POSTS = "write:posts",
}
const rolePermissions: Record<Role, Permission[]> = {
[Role.USER]: [Permission.READ_POSTS, Permission.WRITE_POSTS],
[Role.MODERATOR]: [
Permission.READ_POSTS,
Permission.WRITE_POSTS,
Permission.READ_USERS,
],
[Role.ADMIN]: Object.values(Permission),
};
function hasPermission(userRole: Role, permission: Permission): boolean {
return rolePermissions[userRole]?.includes(permission) ?? false;
}
function requirePermission(...permissions: Permission[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: "Not authenticated" });
}
const hasAllPermissions = permissions.every((permission) =>
hasPermission(req.user.role, permission),
);
if (!hasAllPermissions) {
return res.status(403).json({ error: "Insufficient permissions" });
}
next();
};
}
app.get(
"/api/users",
authenticate,
requirePermission(Permission.READ_USERS),
async (req, res) => {
const users = await db.users.findAll();
res.json({ users });
},
);
Padrão 3: Posse do Recurso (Resource Ownership)
async function requireOwnership(
resourceType: "post" | "comment",
resourceIdParam: string = "id",
) {
return async (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: "Not authenticated" });
}
const resourceId = req.params[resourceIdParam];
if (req.user.role === Role.ADMIN) {
return next();
}
let resource;
if (resourceType === "post") {
resource = await db.posts.findById(resourceId);
} else if (resourceType === "comment") {
resource = await db.comments.findById(resourceId);
}
if (!resource) {
return res.status(404).json({ error: "Resource not found" });
}
if (resource.userId !== req.user.userId) {
return res.status(403).json({ error: "Not authorized" });
}
next();
};
}
app.put(
"/api/posts/:id",
authenticate,
requireOwnership("post"),
async (req, res) => {
const post = await db.posts.update(req.params.id, req.body);
res.json({ post });
},
);
Melhores Práticas de Segurança
Padrão 1: Segurança de Senha
import bcrypt from "bcrypt";
import { z } from "zod";
const passwordSchema = z
.string()
.min(12, "Password must be at least 12 characters")
.regex(/[A-Z]/, "Password must contain uppercase letter")
.regex(/[a-z]/, "Password must contain lowercase letter")
.regex(/[0-9]/, "Password must contain number")
.regex(/[^A-Za-z0-9]/, "Password must contain special character");
async function hashPassword(password: string): Promise<string> {
const saltRounds = 12;
return bcrypt.hash(password, saltRounds);
}
async function verifyPassword(
password: string,
hash: string,
): Promise<boolean> {
return bcrypt.compare(password, hash);
}
app.post("/api/auth/register", async (req, res) => {
try {
const { email, password } = req.body;
passwordSchema.parse(password);
const existingUser = await db.users.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: "Email already registered" });
}
const passwordHash = await hashPassword(password);
const user = await db.users.create({
email,
passwordHash,
});
const tokens = generateTokens(user.id, user.email, user.role);
res.status(201).json({
user: { id: user.id, email: user.email },
...tokens,
});
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ error: error.errors[0].message });
}
res.status(500).json({ error: "Registration failed" });
}
});
Padrão 2: Rate Limiting
import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";
const loginLimiter = rateLimit({
store: new RedisStore({ client: redisClient }),
windowMs: 15 * 60 * 1000,
max: 5,
message: "Too many login attempts, please try again later",
standardHeaders: true,
legacyHeaders: false,
});
const apiLimiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
standardHeaders: true,
});
app.post("/api/auth/login", loginLimiter, async (req, res) => {
});
app.use("/api/", apiLimiter);
Melhores Práticas
- Nunca Armazene Senhas em Texto Puro: Sempre faça hash com bcrypt/argon2
- Use HTTPS: Criptografe os dados em trânsito
- Access Tokens de Curta Duração: 15-30 minutos no máximo
- Cookies Seguros: As flags
httpOnly, secure e sameSite
- Valide Todo o Input: Formato de e-mail, força da senha
- Faça Rate Limit nos Endpoints de Auth: Previna ataques de força bruta (brute force attacks)
- Implemente Proteção contra CSRF: Obrigatório para auth baseada em sessão
- Rotacione Segredos Regularmente: Os secrets do JWT e secrets da sessão
- Logue os Eventos de Segurança: Tentativas de login, autenticações que falharam
- Use MFA Quando Possível: Uma camada extra de segurança
Armadilhas Comuns (Common Pitfalls)
- Senhas Fracas: Faça cumprir políticas de senhas fortes
- JWT no localStorage: Fica vulnerável a XSS. Use cookies
httpOnly
- Nenhuma Expiração no Token: Todos os tokens devem expirar
- Checagem de Auth Só no Client-Side: Sempre valide do lado do servidor
- Redefinição de Senha Insegura: Use tokens seguros que expiram
- Ausência de Rate Limiting: Fica vulnerável à força bruta
- Confiar nos Dados do Client: Sempre faça a validação no servidor
Recursos