From global-plugin
Use when reviewing or editing authentication, sessions, JWT/tokens, RBAC/ABAC logic, or any route/handler/procedure that accesses user data. Do NOT use for infra-level IAM (use `infra-safe-change` / `aws-deploy-safety`). Covers authN flows, session/token hygiene, RBAC/ABAC checks, CSRF, permission inheritance.
npx claudepluginhub lgerard314/global-marketplace --plugin global-pluginThis skill is limited to using the following tools:
Ensure every request is authenticated correctly and authorized on the server for the specific resource, not just the endpoint.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Share bugs, ideas, or general feedback.
Ensure every request is authenticated correctly and authorized on the server for the specific resource, not just the endpoint.
HttpOnly exposes the token to XSS; missing SameSite opens CSRF; not rotating on privilege change allows session fixation.iss, aud, exp, nbf on every request; no symmetric secrets accessible from the client. — Why: a stolen access token's blast radius is bounded by its TTL; unverified claims let an attacker forge a legitimate-looking payload.| Thought | Reality |
|---|---|
| "The UI hides the button, only admins see it" | Any HTTP client ignores the UI; the backend must refuse the request independently. |
| "Same JWT secret client-side and server-side, simpler" | Client-side secrets are public — anyone can forge tokens signed with that key. |
| "Long-lived session, users hate re-logging-in" | Compromise window equals session lifetime; prefer sliding-window short sessions with silent refresh. |
| "We rate-limit the whole API, login is covered" | A global API rate limit is orders of magnitude too lenient to stop credential stuffing on a login endpoint. |
Bad — role check only, any MANAGER can read any order:
// NestJS guard checking role but not ownership
@Injectable()
export class RoleGuard implements CanActivate {
canActivate(ctx: ExecutionContext): boolean {
const req = ctx.switchToHttp().getRequest();
return req.user?.role === 'MANAGER'; // any manager passes — no resource scoping
}
}
Good — resource-scoped check via Prisma ownership lookup:
// orders.guard.ts
@Injectable()
export class OrderOwnerGuard implements CanActivate {
constructor(private readonly prisma: PrismaService) {}
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest<RequestWithUser>();
const orderId = req.params.orderId;
const userId = req.user.id;
const order = await this.prisma.order.findFirst({
where: { id: orderId, ownerId: userId }, // resource-scoped
select: { id: true },
});
return order !== null;
}
}
// Controller usage — guard runs before the handler
@UseGuards(JwtAuthGuard, OrderOwnerGuard)
@Get(':orderId')
getOrder(@Param('orderId') id: string) {
return this.ordersService.findOne(id);
}
Bad — single long-lived JWT, 7-day expiry, no refresh path:
const token = jwt.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET!, {
expiresIn: '7d', // compromise window = 7 days
});
Good — short access token + opaque refresh token stored in HttpOnly cookie:
// auth.service.ts
async issueTokens(userId: string) {
const accessToken = this.jwtService.sign(
{ sub: userId },
{ expiresIn: '15m', issuer: 'api.example.com', audience: 'web' },
);
// Opaque refresh token stored in DB; rotated on every use
const refreshToken = crypto.randomBytes(40).toString('hex');
await this.prisma.refreshToken.create({
data: {
token: await argon2.hash(refreshToken),
userId,
expiresAt: addDays(new Date(), 30),
},
});
return { accessToken, refreshToken };
}
// In the controller: set refresh token as HttpOnly, Secure, SameSite=Strict cookie
@Post('refresh')
async refresh(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
const rawToken = req.cookies['refresh_token'];
const tokens = await this.authService.rotateRefreshToken(rawToken);
res.cookie('refresh_token', tokens.refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 30 * 24 * 60 * 60 * 1000,
path: '/auth/refresh',
});
return { accessToken: tokens.accessToken };
}
Bad — email change accepted on any valid session:
@Patch('email')
@UseGuards(JwtAuthGuard)
async changeEmail(@Body() dto: ChangeEmailDto, @CurrentUser() user: User) {
return this.usersService.updateEmail(user.id, dto.email); // no re-auth
}
Good — step-up check: password must be re-verified before sensitive mutation:
// step-up.guard.ts
@Injectable()
export class StepUpGuard implements CanActivate {
constructor(private readonly usersService: UsersService) {}
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest<RequestWithUser>();
const body = req.body as { currentPassword?: string };
if (!body.currentPassword) {
throw new UnauthorizedException('Current password required for this action');
}
const valid = await this.usersService.verifyPassword(
req.user.id,
body.currentPassword,
);
if (!valid) {
throw new UnauthorizedException('Current password is incorrect');
}
return true;
}
}
@Patch('email')
@UseGuards(JwtAuthGuard, StepUpGuard)
async changeEmail(@Body() dto: ChangeEmailDto, @CurrentUser() user: User) {
return this.usersService.updateEmail(user.id, dto.email);
}
Role-based access control (RBAC) assigns permissions to roles and roles to users. RBAC alone creates horizontal privilege escalation in multi-tenant systems.
Attribute-based access control (ABAC) evaluates policies against attributes of the subject (user), the resource, and the environment (time, IP, device). An ABAC rule such as user.tenantId === resource.tenantId && user.role === 'MANAGER' closes the horizontal gap. The practical implementation in a NestJS + Prisma stack is to embed the ownership check in the database query itself: prisma.order.findFirst({ where: { id, ownerId: userId } }).
Caution: scope queries at the service layer too — guards can be bypassed by internal callers.
Next.js middleware: RBAC routing only; resource ABAC belongs in the handler (middleware can't see the resource).
There are two mainstream defences.
SameSite=Lax minimum; Strict for session/refresh cookies.
Server stores a token; client echoes it in a header. Cross-origin requests can't read the cookie, so attackers can't forge the header.
Don't rely on Referer or CORS as CSRF defence.
Step-up authentication requires a user who already has a valid session to prove their identity again before performing a sensitive action. Stolen sessions shouldn't enable account takeover (email/password change, MFA changes, deletion).
For TOTP-based step-up (used when the account has MFA enrolled), generate a challenge on GET, verify the submitted OTP code on POST, and record a short-lived stepUpAt timestamp in the session. Routes that require step-up check that stepUpAt is within the last N minutes (typically 5–10). Invalidate the stepUpAt timestamp on session rotation.
Never accept the original JWT's iat (issued-at) as a step-up proof — it only tells you when the token was issued, not when the user last re-entered their credentials within this session.
Account enumeration allows an attacker to build a list of valid email addresses by observing differential responses from login, registration, or password-reset endpoints.
All three flows — login, registration, password reset — must return identical HTTP status codes, response bodies, and timing for both the found and not-found cases. For login, return 401 Unauthorized with a generic message such as "Invalid credentials" regardless of whether the email exists or the password is wrong. For password reset, always return 200 OK with "If that email is registered, you will receive a reset link". For registration, if the email is already taken, return 200 OK (or 201 Created) with the same success message as a normal signup, and send the existing account holder a "someone tried to register with your email" notification out-of-band.
Equalise timing: run a dummy hash on the user-not-found path.
In Next.js API routes or server actions, do not return different HTTP status codes for these cases. Redirect only on success leaks state — emit a uniform response shape regardless of outcome.
infra-safe-change for IAM roles and infrastructure-level access policies; secrets-and-config-safety for JWT secret and session secret storage; nextjs-app-structure-guard for Next.js middleware scope and placement.integration-contract-safety's API versioning and inter-service contract concerns; infra-safe-change's AWS IAM policies.Produce a markdown report with these sections:
file:line, severity (low/med/high), category, fix. Include endpoint-level observations as bullets here: for each route/handler/procedure touched, note whether it has an authN guard and a resource-scoped authZ check, and flag any missing or role-only checks as findings with their file:line.findFirst({ where: { id, ownerId } }) over a role-only guard plus an unconditional service fetch.