Passo-a-passo detalhado do skill, referenciando as fases cognitivas:
1SENSE — Identificar mecanismo de auth
```typescript
// Identificar algoritmo e provedor
import jwt from 'jsonwebtoken';
const decoded = jwt.decode(token, { complete: true });
console.log(decoded?.header.alg); // RS256 ou HS256
```
2RECOMMEND — Middleware Next.js completo
```typescript
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify, importSPKI } from 'jose';
import { redis } from './lib/redis';
const PUBLIC_PATHS = ['/api/auth', '/api/health', '/login'];
export async function middleware(req: NextRequest) {
if (PUBLIC_PATHS.some(p => req.nextUrl.pathname.startsWith(p))) {
return NextResponse.next();
}
const token = req.headers.get('Authorization')?.replace('Bearer ', '');
if (!token) {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
}
try {
const publicKey = await importSPKI(process.env.JWT_PUBLIC_KEY!, 'RS256');
const { payload } = await jwtVerify(token, publicKey, {
issuer: process.env.JWT_ISSUER,
audience: process.env.JWT_AUDIENCE,
});
// Verificar blacklist (logout)
if (payload.jti) {
const isRevoked = await redis.get(`blacklist:${payload.jti}`);
if (isRevoked) {
return NextResponse.json({ error: 'Token revoked' }, { status: 401 });
}
}
// Injetar user no header para rotas downstream
const headers = new Headers(req.headers);
headers.set('x-user-id', payload.sub!);
headers.set('x-user-role', String(payload.role ?? 'user'));
return NextResponse.next({ request: { headers } });
} catch (e) {
return NextResponse.json({ error: 'Invalid or expired token' }, { status: 401 });
}
}
```
```typescript
// lib/auth/withRoles.ts
export function withRoles(roles: string[]) {
return function(handler: Function) {
return async (req: Request) => {
const role = req.headers.get('x-user-role');
if (!role || !roles.includes(role)) {
return Response.json({ error: 'Insufficient permissions' }, { status: 403 });
}
return handler(req);
};
};
}
// Uso: export const POST = withRoles(['admin'])(createSkill);
```
4RECOMMEND — Logout com blacklist Redis
```typescript
async function revokeToken(token: string) {
const { payload } = await jwtVerify(token, publicKey);
if (payload.jti && payload.exp) {
const ttl = payload.exp - Math.floor(Date.now() / 1000);
if (ttl > 0) {
await redis.setex(`blacklist:${payload.jti}`, ttl, '1');
}
}
}
```
5EVALUATE — Testes de segurança
```typescript
it('rejects algorithm: none attack', async () => {
const noneToken = createTokenWithAlgNone({ sub: 'user-1', role: 'admin' });
const res = await api.get('/api/protected').set('Authorization', `Bearer ${noneToken}`);
expect(res.status).toBe(401);
});
it('returns 403 (not 401) for insufficient role', async () => {
const token = createToken({ sub: 'user-1', role: 'viewer' });
const res = await api.delete('/api/admin/users/1').set('Authorization', `Bearer ${token}`);
expect(res.status).toBe(403); // authenticated but not authorized
});
```
6REFLECT — Documentar e validar
Testar que `alg: none` é rejeitado (jwtVerify do jose já rejeita)
Confirmar que logs não expõem o token completo — apenas sub e jti
Reportar telemetria via mcp-skillschain