Cookbook: Autenticação JWT com Refresh Token Seguro
Receita completa para implementar autenticação JWT com access token de curta duração e refresh token via HTTP-only cookie, usando Node.js e Angular.
Arquitetura
A estratégia mais segura combina dois tokens:
- Access Token: JWT de curta duração (15min), enviado no header Authorization
- Refresh Token: Token opaco de longa duração (7 dias), armazenado em HTTP-only cookie
Quando o access token expira, o frontend faz uma requisição silenciosa com o refresh token para obter um novo par de tokens.
Backend: Gerar Tokens
class AuthService {
generateTokens(userId: string) {
const accessToken = jwt.sign(
{ sub: userId },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = crypto.randomBytes(64).toString('hex');
// Salvar hash do refresh token no banco
await this.prisma.refreshToken.create({
data: {
token: await bcrypt.hash(refreshToken, 10),
userId,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
},
});
return { accessToken, refreshToken };
}
}
Backend: Endpoint de Refresh
@Post('refresh')
async refresh(@Req() req, @Res() res) {
const refreshToken = req.cookies['refresh_token'];
if (!refreshToken) throw new UnauthorizedException();
const stored = await this.prisma.refreshToken.findFirst({
where: { userId: req.user?.id, expiresAt: { gt: new Date() } },
});
if (!stored || !(await bcrypt.compare(refreshToken, stored.token))) {
throw new UnauthorizedException();
}
// Rotation: invalidar token antigo, gerar novo par
await this.prisma.refreshToken.delete({ where: { id: stored.id } });
const tokens = await this.generateTokens(stored.userId);
res.cookie('refresh_token', tokens.refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000,
path: '/api/auth/refresh',
});
return res.json({ access_token: tokens.accessToken });
}
Frontend: Interceptor Angular
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const token = authService.accessToken();
if (token) {
req = req.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
}
return next(req).pipe(
catchError(error => {
if (error.status === 401 && !req.url.includes('refresh')) {
return authService.refreshToken().pipe(
switchMap(newToken => {
const retryReq = req.clone({
setHeaders: { Authorization: `Bearer ${newToken}` },
});
return next(retryReq);
}),
);
}
return throwError(() => error);
}),
);
};
Checklist de Segurança
- Access token curto (15min max)
- Refresh token em HTTP-only cookie (não acessível via JS)
- Flag Secure em produção (HTTPS only)
- SameSite=Strict para prevenir CSRF
- Rotation de refresh token a cada uso
- Limitar path do cookie ao endpoint de refresh
- Salvar apenas hash do refresh token no banco



