jwt-security
JSON Web Token security best practices. Use this skill when implementing JWT authentication, validating tokens, or reviewing JWT usage. Activate when: JWT, JSON Web Token, token authentication, bearer token, refresh token, token validation, JWT secret, token expiry.
What this skill does
# JWT Security
**Secure implementation of JSON Web Tokens for authentication.**
## When to Use
- Implementing JWT authentication
- Reviewing existing JWT code
- Setting up refresh token rotation
- Debugging JWT issues
- Migrating to JWT-based auth
## JWT Vulnerabilities
| Vulnerability | Risk | Description |
|--------------|------|-------------|
| Algorithm None | CRITICAL | Accepting unsigned tokens |
| Algorithm Confusion | CRITICAL | RS256 → HS256 attack |
| Weak Secret | HIGH | Brute-forceable secrets |
| No Expiration | HIGH | Tokens valid forever |
| Sensitive Data in Payload | MEDIUM | JWT payload is base64, not encrypted |
| Token Leakage | HIGH | Exposed in logs/URLs |
## Secure Implementation
### 1. Token Generation
```javascript
const jwt = require('jsonwebtoken');
const JWT_CONFIG = {
accessSecret: process.env.JWT_ACCESS_SECRET, // 256+ bit random string
refreshSecret: process.env.JWT_REFRESH_SECRET,
accessExpiry: '15m', // Short-lived
refreshExpiry: '7d', // Longer-lived
algorithm: 'HS256', // Or RS256 for asymmetric
issuer: 'your-app-name',
audience: 'your-app-users'
};
function generateAccessToken(user) {
const payload = {
sub: user.id, // Subject (user ID)
email: user.email, // Only non-sensitive data
role: user.role,
// Don't include: password, SSN, credit card, etc.
};
return jwt.sign(payload, JWT_CONFIG.accessSecret, {
algorithm: JWT_CONFIG.algorithm,
expiresIn: JWT_CONFIG.accessExpiry,
issuer: JWT_CONFIG.issuer,
audience: JWT_CONFIG.audience,
jwtid: crypto.randomUUID() // Unique token ID
});
}
function generateRefreshToken(user) {
const payload = {
sub: user.id,
type: 'refresh',
family: crypto.randomUUID() // For refresh token rotation
};
return jwt.sign(payload, JWT_CONFIG.refreshSecret, {
algorithm: JWT_CONFIG.algorithm,
expiresIn: JWT_CONFIG.refreshExpiry,
issuer: JWT_CONFIG.issuer
});
}
```
### 2. Token Verification (CRITICAL)
```javascript
function verifyAccessToken(token) {
try {
// CRITICAL: Always specify allowed algorithms
return jwt.verify(token, JWT_CONFIG.accessSecret, {
algorithms: [JWT_CONFIG.algorithm], // Whitelist!
issuer: JWT_CONFIG.issuer,
audience: JWT_CONFIG.audience,
complete: true // Returns header + payload
});
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new AuthError('Token expired', 'TOKEN_EXPIRED');
}
if (error instanceof jwt.JsonWebTokenError) {
throw new AuthError('Invalid token', 'INVALID_TOKEN');
}
throw error;
}
}
// VULNERABLE - Never do this!
// jwt.verify(token, secret); // Accepts any algorithm!
// jwt.decode(token); // No verification at all!
```
### 3. Authentication Middleware
```javascript
async function authenticateJWT(req, res, next) {
// Extract token from header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
const decoded = verifyAccessToken(token);
// Optional: Check if token is blacklisted
if (await isTokenBlacklisted(decoded.payload.jti)) {
return res.status(401).json({ error: 'Token revoked' });
}
// Attach user to request
req.user = {
id: decoded.payload.sub,
email: decoded.payload.email,
role: decoded.payload.role
};
next();
} catch (error) {
if (error.code === 'TOKEN_EXPIRED') {
return res.status(401).json({
error: 'Token expired',
code: 'TOKEN_EXPIRED'
});
}
return res.status(401).json({ error: 'Invalid token' });
}
}
```
### 4. Refresh Token Rotation
```javascript
// Store refresh tokens in database
const refreshTokenSchema = {
id: 'uuid',
userId: 'uuid',
tokenHash: 'string', // Store hash, not token
family: 'string', // For rotation detection
expiresAt: 'datetime',
revokedAt: 'datetime?',
replacedBy: 'uuid?'
};
async function refreshTokens(refreshToken) {
// Verify refresh token
let decoded;
try {
decoded = jwt.verify(refreshToken, JWT_CONFIG.refreshSecret, {
algorithms: [JWT_CONFIG.algorithm]
});
} catch {
throw new AuthError('Invalid refresh token');
}
// Find token in database
const tokenHash = hashToken(refreshToken);
const storedToken = await db.refreshTokens.findOne({
tokenHash,
revokedAt: null
});
if (!storedToken) {
// Token not found or already revoked
// Possible token reuse attack - revoke entire family
await db.refreshTokens.updateMany(
{ family: decoded.family },
{ revokedAt: new Date() }
);
throw new AuthError('Refresh token reuse detected');
}
// Check expiration
if (new Date() > storedToken.expiresAt) {
throw new AuthError('Refresh token expired');
}
// Rotate: revoke old, create new
const user = await db.users.findById(decoded.sub);
const newAccessToken = generateAccessToken(user);
const newRefreshToken = generateRefreshToken(user);
// Revoke old refresh token
await db.refreshTokens.update(storedToken.id, {
revokedAt: new Date(),
replacedBy: newRefreshToken.id
});
// Store new refresh token
await db.refreshTokens.create({
userId: user.id,
tokenHash: hashToken(newRefreshToken),
family: decoded.family, // Same family for rotation tracking
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
});
return {
accessToken: newAccessToken,
refreshToken: newRefreshToken
};
}
```
### 5. Token Revocation
```javascript
// Blacklist for immediate revocation
const tokenBlacklist = new Map(); // In production, use Redis
async function revokeToken(token) {
const decoded = jwt.decode(token);
if (decoded && decoded.jti) {
// Store until token would naturally expire
const ttl = decoded.exp * 1000 - Date.now();
await redis.setex(`blacklist:${decoded.jti}`, ttl / 1000, '1');
}
}
async function isTokenBlacklisted(jti) {
return await redis.exists(`blacklist:${jti}`);
}
// Logout endpoint
app.post('/logout', authenticateJWT, async (req, res) => {
// Revoke access token
await revokeToken(req.headers.authorization.substring(7));
// Revoke all refresh tokens for user
await db.refreshTokens.updateMany(
{ userId: req.user.id },
{ revokedAt: new Date() }
);
res.json({ success: true });
});
```
### 6. Asymmetric Keys (RS256)
```javascript
const fs = require('fs');
// For distributed systems or when verifier != issuer
const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');
function generateTokenRS256(user) {
return jwt.sign(
{ sub: user.id },
privateKey,
{
algorithm: 'RS256',
expiresIn: '15m',
issuer: 'auth-service'
}
);
}
function verifyTokenRS256(token) {
return jwt.verify(token, publicKey, {
algorithms: ['RS256'], // CRITICAL: Only allow RS256
issuer: 'auth-service'
});
}
```
```bash
# Generate RSA key pair
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
```
## Token Storage (Client-Side)
```javascript
// BEST: HttpOnly cookie (for web apps)
res.cookie('accessToken', token, {
httpOnly: true, // No JS access
secure: true, // HTTPS only
sameSite: 'strict',
maxAge: 900000 // 15 minutes
});
// OK: Memory (for SPAs, lost on refresh)
let accessToken = null;
function setToken(token) {
accessToken = token;
}
// AVOID: localStorage (XSS vulnerable)
// localStorage.setItem('token', token); // DON'T!
```
## Common Mistakes
```javascript
// MISTAKE 1: Not specifying algorithm
jwt.verify(token, secret); // Vulnerable to algorithm switching!
// MISTAKE 2: Using decode instead of verify
const user = jwt.decode(token); // No signature check!
// MISTAKE 3: Sensitive data in paRelated in Security
mac-ops
IncludedComprehensive macOS workstation operations — diagnose kernel panics, identify failing drives, audit launchd startup items, decode wake reasons, triage TCC permission denials, manage APFS snapshots, recover from no-boot. Use for: Mac is slow, slow bootup, won't boot, kernel panic, kernel_task hot, mds_stores CPU, photoanalysisd, cloudd, login loop, gray screen, sleep wake failure, drive failing, IO errors, APFS snapshots eating space, Time Machine local snapshots, Spotlight indexing, launchd, LaunchAgent, LaunchDaemon, login items, TCC permissions, Full Disk Access, Screen Recording denied, Gatekeeper, quarantine, com.apple.quarantine, app is damaged, helper tool, /Library/PrivilegedHelperTools, pmset, wake reasons, dark wake, sysdiagnose, panic.ips, DiagnosticReports, configuration profile, MDM profile, remote diagnostics over SSH.
a11y-audit
IncludedRun accessibility audits on web projects combining automated scanning (axe-core, Lighthouse) with WCAG 2.1 AA compliance mapping, manual check guidance, and structured reporting. Output is configurable: markdown report only, markdown plus machine-readable JSON, or markdown plus issue tracker integration. Use this skill whenever the user mentions "accessibility audit", "a11y audit", "WCAG audit", "accessibility check", "compliance scan", or asks to check a web project for accessibility issues. Also trigger when the user wants to verify WCAG conformance or map findings to a specific standard (CAN-ASC-6.2, EN 301 549, ADA/AODA).
erpclaw
IncludedAI-native ERP system with self-extending OS. Full accounting, invoicing, inventory, purchasing, tax, billing, HR, payroll, advanced accounting (ASC 606/842, intercompany, consolidation), and financial reporting. 413 actions across 14 domains, 43 expansion modules. Constitutional guardrails, adversarial audit, schema migration. Double-entry GL, immutable audit trail, US GAAP.
assess
IncludedAssesses and rates quality 0-10 across multiple dimensions (correctness, maintainability, security, performance, testability, simplicity) with pros/cons analysis. Compares against project conventions and prior decisions from memory. Produces structured evaluation reports with actionable improvement suggestions. Use when evaluating code, designs, architectures, or comparing alternative approaches.
spring-boot-security-jwt
IncludedProvides JWT authentication and authorization patterns for Spring Boot 3.5.x covering token generation with JJWT, Bearer/cookie authentication, database/OAuth2 integration, and RBAC/permission-based access control using Spring Security 6.x. Use when implementing authentication or authorization in Spring Boot applications.
code-hardcode-audit
IncludedDetect hardcoded values, magic numbers, and leaked secrets. TRIGGERS - hardcode audit, magic numbers, PLR2004, secret scanning.