Secure Auth, Permissions, and Input Validation
Implement secure login, sessions, resource permissions, and input validation with working code and targeted abuse-case tests.
Before & after
Same prompt, same agent — the only difference is the skill.
I can help you think through JWT authentication. You'd need to handle token generation, validation, and refresh logic. You'll want to consider where to store the secret key, how long tokens should be valid, and how to handle token expiration. You'd also want to make sure you're hashing passwords properly if you're building user registration. There are a lot of best practices around JWT—using strong algorithms, securing the keys, handling edge cases. If you can tell me what framework you're using, I can give you more specific guidance on implementation approaches.
JWT AUTHENTICATION IMPLEMENTATION
FILE STRUCTURE:
/app/api/auth/
login.ts (POST: email + password → JWT token)
token-validate.ts (POST: validate token + return claims)
refresh.ts (POST: refresh_token → new JWT)
logout.ts (POST: invalidate token)
STEP 1: PASSWORD HASHING (Before token generation)
Library: bcrypt (or argon2 for higher security)
Code (Node.js):
const bcrypt = require("bcrypt");
const SALT_ROUNDS = 12;
// On signup:
const passwordHash = await bcrypt.hash(userPassword, SALT_ROUNDS);
await db.users.create({ email, passwordHash });
// On login:
const user = await db.users.findByEmail(email);
const isValid = await bcrypt.compare(userPassword, user.passwordHash);
if (!isValid) throw new Error("Invalid credentials");
Rationale: bcrypt with 12 rounds = ~250ms per hash (slows down brute force attacks). Never store plaintext passwords.
STEP 2: JWT TOKEN GENERATION
Library: jsonwebtoken (Node.js)
Secret key storage: Use environment variable (JWT_SECRET in .env.local)
Minimum secret length: 32 bytes (256 bits)
Code:
const jwt = require("jsonwebtoken");
const JWT_SECRET = process.env.JWT_SECRET;
const JWT_EXPIRY = "15m"; // Short-lived access token
// Generate access token (short-lived)
const accessToken = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role, // for authorization checks
},
JWT_SECRET,
{ expiresIn: JWT_EXPIRY, algorithm: "HS256" }
);
// Generate refresh token (long-lived, stored in DB)
const refreshToken = jwt.sign(
{ userId: user.id, tokenVersion: user.tokenVersion },
JWT_SECRET,
{ expiresIn: "7d" }
);
// Store refresh token in database (tied to user)
await db.refreshTokens.create({ userId: user.id, token: refreshToken });
Return to client:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 900 // seconds (15 min)
}
STEP 3: TOKEN VALIDATION MIDDLEWARE
Code (attach to protected routes):
const validateJWT = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing token" });
}
const token = authHeader.slice(7);
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded; // Attach user claims to request
next();
} catch (err) {
if (err.name === "TokenExpiredError") {
return res.status(401).json({ error: "Token expired. Use refresh endpoint." });
}
return res.status(403).json({ error: "Invalid token" });
}
};
Usage on protected endpoints:
app.post("/api/user-data", validateJWT, (req, res) => {
// req.user.userId is now available
res.json({ userId: req.user.userId, data: "your data" });
});
STEP 4: TOKEN REFRESH LOGIC
Code:
app.post("/api/auth/refresh", async (req, res) => {
const { refreshToken } = req.body;
// Validate refresh token signature
try {
const decoded = jwt.verify(refreshToken, JWT_SECRET);
} catch (err) {
return res.status(401).json({ error: "Refresh token invalid" });
}
// Check if refresh token exists in database (prevents token reuse after logout)
const tokenRecord = await db.refreshTokens.findOne({ token: refreshToken });
if (!tokenRecord) {
return res.status(401).json({ error: "Token revoked" });
}
// Verify token version (prevents old refresh tokens after password reset)
const user = await db.users.findById(decoded.userId);
if (user.tokenVersion !== decoded.tokenVersion) {
return res.status(401).json({ error: "Token version mismatch" });
}
// Generate new access token
const newAccessToken = jwt.sign(
{ userId: user.id, email: user.email, role: user.role },
JWT_SECRET,
{ expiresIn: "15m" }
);
res.json({ accessToken: newAccessToken, expiresIn: 900 });
});
Rationale: Refresh tokens are stored in DB so they can be revoked. Access tokens are short-lived (15 min) to limit exposure if stolen.
STEP 5: LOGOUT (REVOKE REFRESH TOKEN)
Code:
app.post("/api/auth/logout", validateJWT, async (req, res) => {
const { refreshToken } = req.body;
await db.refreshTokens.deleteWhere({ token: refreshToken });
res.json({ message: "Logged out" });
});
Rationale: Delete the refresh token from database so it can't be reused.
STEP 6: SECURITY HARDENING
SECURE TRANSPORT:
• Always send JWT over HTTPS (never HTTP)
• In API Gateway: Enforce HTTPS, set Strict-Transport-Security header
STORAGE ON CLIENT:
• Option A: Store in secure httpOnly cookie (immune to XSS, but vulnerable to CSRF)
res.cookie("accessToken", token, { httpOnly: true, secure: true, sameSite: "Strict" });
• Option B: Store in localStorage (vulnerable to XSS, immune to CSRF)
• Recommendation: Use httpOnly cookie + CSRF token
KEY ROTATION:
• Rotate JWT_SECRET every 90 days
• Keep old secret for 7 days (allows in-flight tokens to validate)
• Invalidate all refresh tokens when rotating (force re-login)
ALGORITHM CHOICE:
• Use HS256 (HMAC-SHA256) for symmetric signing (single secret)
• Use RS256 (RSA) if you need asymmetric signing (public key verification from other services)
• Never use HS256 + public verification (signature is trivial to forge)
CLAIM VALIDATION:
• Always validate token signature (jwt.verify does this)
• Always check expiration claim (jwt.verify does this)
• Always verify the algorithm matches (jwt.verify does this by default)
EXAMPLE TEST CASE:
Describe("JWT Auth", () => {
it("should reject expired token", () => {
const expiredToken = jwt.sign({ userId: 1 }, JWT_SECRET, { expiresIn: "-1s" });
const res = validateJWT({ headers: { authorization: `Bearer ${expiredToken}` } });
expect(res.status).toBe(401);
});
it("should accept valid token", () => {
const token = jwt.sign({ userId: 1 }, JWT_SECRET, { expiresIn: "15m" });
const res = validateJWT({ headers: { authorization: `Bearer ${token}` } });
expect(res.user.userId).toBe(1);
});
});
DEPLOYMENT CHECKLIST:
✓ JWT_SECRET set in production .env (32+ bytes, random)
✓ HTTPS enforced on all endpoints
✓ Refresh tokens stored in database (not in JWT)
✓ Token expiration validated on each request
✓ Password hashing with bcrypt/argon2 (not plain text)
✓ Unit tests for token refresh, expiration, revocation
✓ Error messages don't leak user existence ("Invalid credentials" not "User not found")
✓ Rate limiting on /auth endpoints (prevent brute force: max 5 login attempts per minute per IP)
Note: This is a security implementation reference, not comprehensive security advice. Have your security team review before production.About this skill
Your login works, but can a user change an ID and edit someone else's data? This skill turns security requirements into concrete application code, covering password handling, session revocation, tenant boundaries, input validation, and the failure paths that happy-path examples leave out.
What it does
- Implements password login, secure cookies or JWT validation, logout, and credential recovery with explicit expiration and revocation behavior.
- Places ownership, tenant, and role checks inside protected data operations to prevent unauthorized reads and writes.
- Builds server-side schemas that reject unexpected fields, enforce limits, and prevent mass assignment.
- Uses parameterized database operations and context-appropriate defenses for HTML, URLs, uploads, and outbound requests.
- Configures CORS, CSRF protections, and security headers for the actual browser and deployment model.
- Supplies focused tests for cross-user access, tampered credentials, rejected input, and session lifecycle failures.
What you get
An implementation or focused patch in your application's stack, with the assumptions, security decisions, and abuse-case tests needed to review it. Missing project details produce a labeled provisional solution and a few consequential questions.
Best for
Developers adding login, protected API endpoints, multitenant permissions, or user-submitted data handling to an existing application.
SKILL.md preview
---
name: secure-auth-permissions-and-input-validation
description: Implement authentication, session management, resource-level authorization, and server-side input validation by tracing trust boundaries, enforcing access in data operations, and testing hostile requests. Use when the user says "implement secure login", "add permissions to this endpoint", "users can access each other's records", "my JWT logout keeps leaving tokens valid", "validate this request body", "fix our CORS and CSRF setup", or "harden this API against injection".
---
# Secure Auth, Permissions, and Input Validation
… (sign up to view the full skill)More development skills
View all Development skills →Threat Hunting and IOC Triage
Turn telemetry and threat indicators into scoped hunts, evidence-backed findings, and prioritized investigation steps.
Secrets Vault Setup, Rotation, and Access Audits
Set up managed secret stores, wire workload access, rotate credentials safely, and audit least-privilege policies.
Application Penetration Test and Fix Plan
Assess application and API vulnerabilities with reproducible evidence, exploitability rankings, specific fixes, and retest steps.
Application Security Triage and Remediation
Turn code, dependency, and control findings into evidence-backed priorities, safe fixes, and verification plans.