Secure Auth, Permissions, and Input Validation

Implement secure login, sessions, resource permissions, and input validation with working code and targeted abuse-case tests.

Development / Securityauthenticationauthorization
by AgentVoltv1.0.0
Free to sign up · every skill included with AgentVolt Pro

Before & after

Same prompt, same agent — the only difference is the skill.

Prompt“Implement JWT token authentication for our API”
Beforegeneric AI agent
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.
Afterwith Secure Auth, Permissions, and Input Validation
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

SKILL.md
---
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)
Sign up to view, copy, and install the full skill

More development skills

View all Development skills →