Files
agent-skills/skills/nestjs-best-practices/rules/security-auth-jwt.md
Jason Woltje 861b28b965 feat: Expand fleet to 23 skills across all domains
New skills (14):
- nestjs-best-practices: 40 priority-ranked rules (kadajett)
- fastapi: Pydantic v2, async SQLAlchemy, JWT auth (jezweb)
- architecture-patterns: Clean Architecture, Hexagonal, DDD (wshobson)
- python-performance-optimization: Profiling and optimization (wshobson)
- ai-sdk: Vercel AI SDK streaming and agent patterns (vercel)
- create-agent: Modular agent architecture with OpenRouter (openrouterteam)
- proactive-agent: WAL Protocol, compaction recovery, self-improvement (halthelobster)
- brand-guidelines: Brand identity enforcement (anthropics)
- ui-animation: Motion design with accessibility (mblode)
- marketing-ideas: 139 ideas across 14 categories (coreyhaines31)
- pricing-strategy: SaaS pricing and tier design (coreyhaines31)
- programmatic-seo: SEO at scale with playbooks (coreyhaines31)
- competitor-alternatives: Comparison page architecture (coreyhaines31)
- referral-program: Referral and affiliate programs (coreyhaines31)

README reorganized by domain: Code Quality, Frontend, Backend,
Auth, AI/Agent Building, Marketing, Design, Meta.

Mosaic Stack is not limited to coding — the Orchestrator serves
coding, business, design, marketing, writing, logistics, and analysis.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:22:53 -06:00

147 lines
4.0 KiB
Markdown

---
title: Implement Secure JWT Authentication
impact: CRITICAL
impactDescription: Essential for secure APIs
tags: security, jwt, authentication, tokens
---
## Implement Secure JWT Authentication
Use `@nestjs/jwt` with `@nestjs/passport` for authentication. Store secrets securely, use appropriate token lifetimes, implement refresh tokens, and validate tokens properly. Never expose sensitive data in JWT payloads.
**Incorrect (insecure JWT implementation):**
```typescript
// Hardcode secrets
@Module({
imports: [
JwtModule.register({
secret: 'my-secret-key', // Exposed in code
signOptions: { expiresIn: '7d' }, // Too long
}),
],
})
export class AuthModule {}
// Store sensitive data in JWT
async login(user: User): Promise<{ accessToken: string }> {
const payload = {
sub: user.id,
email: user.email,
password: user.password, // NEVER include password!
ssn: user.ssn, // NEVER include sensitive data!
isAdmin: user.isAdmin, // Can be tampered if not verified
};
return { accessToken: this.jwtService.sign(payload) };
}
// Skip token validation
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'my-secret',
});
}
async validate(payload: any): Promise<any> {
return payload; // No validation of user existence
}
}
```
**Correct (secure JWT with refresh tokens):**
```typescript
// Secure JWT configuration
@Module({
imports: [
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get<string>('JWT_SECRET'),
signOptions: {
expiresIn: '15m', // Short-lived access tokens
issuer: config.get<string>('JWT_ISSUER'),
audience: config.get<string>('JWT_AUDIENCE'),
},
}),
}),
PassportModule.register({ defaultStrategy: 'jwt' }),
],
})
export class AuthModule {}
// Minimal JWT payload
@Injectable()
export class AuthService {
async login(user: User): Promise<TokenResponse> {
// Only include necessary, non-sensitive data
const payload: JwtPayload = {
sub: user.id,
email: user.email,
roles: user.roles,
iat: Math.floor(Date.now() / 1000),
};
const accessToken = this.jwtService.sign(payload);
const refreshToken = await this.createRefreshToken(user.id);
return { accessToken, refreshToken, expiresIn: 900 };
}
private async createRefreshToken(userId: string): Promise<string> {
const token = randomBytes(32).toString('hex');
const hashedToken = await bcrypt.hash(token, 10);
await this.refreshTokenRepo.save({
userId,
token: hashedToken,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
});
return token;
}
}
// Proper JWT strategy with validation
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private config: ConfigService,
private usersService: UsersService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get<string>('JWT_SECRET'),
ignoreExpiration: false,
issuer: config.get<string>('JWT_ISSUER'),
audience: config.get<string>('JWT_AUDIENCE'),
});
}
async validate(payload: JwtPayload): Promise<User> {
// Verify user still exists and is active
const user = await this.usersService.findById(payload.sub);
if (!user || !user.isActive) {
throw new UnauthorizedException('User not found or inactive');
}
// Verify token wasn't issued before password change
if (user.passwordChangedAt) {
const tokenIssuedAt = new Date(payload.iat * 1000);
if (tokenIssuedAt < user.passwordChangedAt) {
throw new UnauthorizedException('Token invalidated by password change');
}
}
return user;
}
}
```
Reference: [NestJS Authentication](https://docs.nestjs.com/security/authentication)