agentleFS
jars-demojars-demo/jars-skills/skills/auth/SKILL.md

auth

Skill0 starsChanged 32 days agoMITView on GitHub

Implement and review authentication and authorization in web applications. Use when the user asks to add login, implement JWT, set up sessions, add OAuth, protect a route, implement role-based access control (RBAC), fix an auth bug, or review auth code for security issues. Never cuts corners on security — always reads the existing auth setup before adding to it.

  • Reads credentials

20 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use github-issues, ui-design and 3 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • SentrySkill

    github-issues

    Create, update, comment on, label, and inspect GitHub issues with concise, evidence-backed content. Use when users ask to open, edit, view, close, reopen, or triage GitHub issues — including tracking bugs, features, or tasks. Prefer this skill over generic repository tools for issue operations; do not use for pull requests, branches, pushes, or PR creation order questions.

    Also in 3 projects that use this

  • SentrySkill

    ui-design

    Also in 3 projects that use this

  • MicrosoftSkill

    typescript

    TypeScript best practices for this monorepo. Use this skill when: (1) writing new TypeScript code, (2) reviewing TypeScript code, (3) working with API responses, fetch calls, or JSON parsing, (4) adding or modifying types and interfaces.

    Also in 3 projects that use this

  • 0xSeroSkill

    security

    Also in 3 projects that use this

  • NVIDIASkill

    testing

    Run Python and Rust tests for physicsnemo-curator using uv + pytest with coverage reporting via pytest-cov, and cargo-nextest for Rust tests. Includes benchmark workflows with pytest-benchmark and criterion.

    Also in 4 projects that use this

The file

We split this skill into the rules it gives an agent

12 rules. 0 are common, 9 are distinctive, and 3 are very specific to this project.

  • 0

    Common

    Most of the 17 edited versions we read keep these.

  • 9

    Distinctive

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Read the existing auth setup first: Never add a second auth system alongside one that already exists.

    line 13
  2. Very specific:

    Never roll your own crypto: Use battle-tested libraries: bcrypt/argon2 for passwords, established JWT libraries for tokens, the platform's …

    line 15
  3. Distinctive:

    Authorization checks belong in the backend: Never trust the client to enforce what a user can access.

    line 17
  4. Distinctive:

    Fail closed: When in doubt (missing token, ambiguous role, unexpected state), deny access.

    line 19
  5. Distinctive:

    Always hash with bcrypt or argon2.

    line 53
  6. Distinctive:

    Before hashing, enforce a minimum length (8+ characters).

    line 91
  7. Very specific:

    Keep the payload minimal: sub (subject = user ID), role, exp.

    line 116
  8. Distinctive:

    Use a strong secret (32+ random bytes).

    line 118
  9. Distinctive:

    Use sessions when you need instant revocation (e.g., force logout on password change).

    line 182
  10. Distinctive:

    Always check that the authenticated user owns the resource they're accessing.

    line 277
  11. Distinctive:

    Use an established library.

    line 306
  12. Very specific:

    Never store OAuth access_token long-term.

    line 345

“Common” is measured: kept by at least 6 in 10 of the 17 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Auth

Implement authentication (who are you?) and authorization (what are you allowed to do?) correctly and completely. Auth bugs are security incidents. Read before writing.

Ground rules

  • Read the existing auth setup first. Never add a second auth system alongside one that already exists.
  • Never roll your own crypto. Use battle-tested libraries: bcrypt/argon2 for passwords, established JWT libraries for tokens, the platform's OAuth library for SSO.
  • Authorization checks belong in the backend. Never trust the client to enforce what a user can access. Always verify on the server.
  • Fail closed. When in doubt (missing token, ambiguous role, unexpected state), deny access. Never default to "allow".
  • Never log tokens, passwords, or credentials — even in debug statements.
  • If you find a real auth vulnerability, stop and flag it clearly before doing anything else.

Step 0: Read the existing auth

# Find the auth layer
grep -r "passport\|jsonwebtoken\|jwt\|bcrypt\|argon2\|session\|cookie\|oauth\|middleware.*auth\|auth.*middleware" \
  --include="*.ts" --include="*.js" --include="*.py" --include="*.go" -l | head -10

# Find route protection patterns
grep -rn "requireAuth\|isAuthenticated\|authMiddleware\|Authorize\|@login_required\|@require_auth" \
  --include="*.ts" --include="*.js" --include="*.py" --include="*.go" | head -20

# Check the user model / schema
cat prisma/schema.prisma
cat app/models/user.py
cat src/entities/User.ts

Understand: how tokens or sessions are issued, where they're validated, and how routes are protected before adding anything.


Passwords

Hashing

Always hash with bcrypt or argon2. Never SHA-256, MD5, or plain storage.

// Node.js — bcrypt
import bcrypt from "bcrypt";

const SALT_ROUNDS = 12;  // minimum 10; 12 is a good default

async function hashPassword(plain: string): Promise<string> {
  return bcrypt.hash(plain, SALT_ROUNDS);
}

async function verifyPassword(plain: string, hash: string): Promise<boolean> {
  return bcrypt.compare(plain, hash);
}
# Python — bcrypt
import bcrypt

def hash_password(plain: str) -> str:
    return bcrypt.hashpw(plain.encode(), bcrypt.gensalt(rounds=12)).decode()

def verify_password(plain: str, hashed: str) -> bool:
    return bcrypt.checkpw(plain.encode(), hashed.encode())

Argon2 is preferred for new projects (winner of the Password Hashing Competition):

import argon2 from "argon2";
const hash = await argon2.hash(plain);
const valid = await argon2.verify(hash, plain);

Validation before hashing

Before hashing, enforce a minimum length (8+ characters). Do not enforce arbitrary complexity rules (mandatory symbols, max length < 64) — they reduce entropy. Do check against a breached-password list for high-value apps.


JWT (stateless tokens)

Issuing

import jwt from "jsonwebtoken";

const ACCESS_TOKEN_EXPIRY = "15m";   // short-lived
const REFRESH_TOKEN_EXPIRY = "7d";   // longer-lived, stored securely

function issueAccessToken(userId: string, role: string): string {
  return jwt.sign(
    { sub: userId, role },            // minimal claims — no PII in the payload
    process.env.JWT_SECRET!,
    { expiresIn: ACCESS_TOKEN_EXPIRY, algorithm: "HS256" }
  );
}
  • Keep the payload minimal: sub (subject = user ID), role, exp. No email, no name, no permissions lists — those belong in the database.
  • Use a strong secret (32+ random bytes). Load from environment, never hardcode.
  • For multi-service systems, prefer RS256 (asymmetric) so services can verify without sharing the signing secret.

Verifying

function verifyAccessToken(token: string): { sub: string; role: string } {
  // jwt.verify throws if the token is expired, tampered, or uses the wrong algorithm
  const payload = jwt.verify(token, process.env.JWT_SECRET!, {
    algorithms: ["HS256"],   // pin the algorithm — prevents "none" algorithm attacks
  });
  return payload as { sub: string; role: string };
}

Refresh token rotation

Access tokens are short-lived. Refresh tokens let users stay logged in without re-entering credentials.

// On login: issue both tokens, persist the refresh token
async function login(userId: string, role: string) {
  const accessToken = issueAccessToken(userId, role);
  const refreshToken = crypto.randomBytes(64).toString("hex");  // opaque, not JWT

  // Store hashed refresh token in DB with expiry
  await db.refreshToken.create({
    data: {
      userId,
      tokenHash: await bcrypt.hash(refreshToken, 10),
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
    },
  });

  return { accessToken, refreshToken };
}

// On refresh: validate, rotate (delete old, issue new)
async function refreshAccessToken(refreshToken: string) {
  const stored = await db.refreshToken.findMany({ where: { expiresAt: { gt: new Date() } } });
  const match = await Promise.any(
    stored.map(async (r) => {
      if (await bcrypt.compare(refreshToken, r.tokenHash)) return r;
      throw new Error("no match");
    })
  ).catch(() => null);

  if (!match) throw new UnauthorizedError("Invalid or expired refresh token");

  // Rotate: delete the used token, issue a new one
  await db.refreshToken.delete({ where: { id: match.id } });
  return login(match.userId, match.role);
}

Store refresh tokens in an httpOnly, Secure, SameSite=Strict cookie — not in localStorage.


Sessions (server-side)

Use sessions when you need instant revocation (e.g., force logout on password change).

// Express + express-session
import session from "express-session";
import connectPgSimple from "connect-pg-simple";

const PgSession = connectPgSimple(session);

app.use(session({
  store: new PgSession({ pool }),         // persist in DB, not memory
  secret: process.env.SESSION_SECRET!,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,    // not accessible from JS
    secure: process.env.NODE_ENV === "production",   // HTTPS only in prod
    sameSite: "strict",
    maxAge: 7 * 24 * 60 * 60 * 1000,   // 7 days
  },
}));

On login: req.session.userId = user.id On logout: req.session.destroy() On password change or suspicious activity: delete all sessions for the user.


Route protection middleware

Express (TypeScript)

import { Request, Response, NextFunction } from "express";

export function requireAuth(req: Request, res: Response, next: NextFunction) {
  const token = req.cookies.accessToken ?? req.headers.authorization?.split(" ")[1];
  if (!token) return res.status(401).json({ error: "Authentication required" });

  try {
    req.user = verifyAccessToken(token);
    next();
  } catch {
    return res.status(401).json({ error: "Invalid or expired token" });
  }
}

export function requireRole(...roles: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) return res.status(401).json({ error: "Authentication required" });
    if (!roles.includes(req.user.role)) return res.status(403).json({ error: "Forbidden" });
    next();
  };
}

// Usage
router.get("/admin/users", requireAuth, requireRole("admin"), listUsers);
router.delete("/posts/:id", requireAuth, deletePost);

FastAPI (Python)

from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

security = HTTPBearer()

async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
    try:
        payload = verify_access_token(credentials.credentials)
        user = await db.get_user(payload["sub"])
        if not user:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
        return user
    except Exception:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")

def require_role(*roles: str):
    async def check(current_user=Depends(get_current_user)):
        if current_user.role not in roles:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden")
        return current_user
    return check

# Usage
@app.get("/admin/users", dependencies=[Depends(require_role("admin"))])
async def list_users(): ...

Authorization: resource ownership

Always check that the authenticated user owns the resource they're accessing. Role checks alone are not enough.

// Bad — any authenticated user can delete any post
router.delete("/posts/:id", requireAuth, async (req, res) => {
  await db.post.delete({ where: { id: req.params.id } });
  res.status(204).send();
});

// Good — verify ownership before acting
router.delete("/posts/:id", requireAuth, async (req, res) => {
  const post = await db.post.findUnique({ where: { id: req.params.id } });
  if (!post) return res.status(404).json({ error: "Not found" });
  if (post.authorId !== req.user.sub && req.user.role !== "admin") {
    return res.status(403).json({ error: "Forbidden" });
  }
  await db.post.delete({ where: { id: req.params.id } });
  res.status(204).send();
});

This pattern (Insecure Direct Object Reference — IDOR) is one of the most common auth vulnerabilities. Check it on every endpoint that takes a resource ID.


OAuth / SSO (third-party login)

Use an established library. Don't implement OAuth flows manually.

// Next.js — NextAuth.js / Auth.js
// next-auth handles token exchange, session management, and provider config

import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    GitHub({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    session({ session, token }) {
      session.user.id = token.sub!;
      return session;
    },
  },
});
# FastAPI — authlib
from authlib.integrations.starlette_client import OAuth

oauth = OAuth()
oauth.register(
    name="google",
    client_id=os.getenv("GOOGLE_CLIENT_ID"),
    client_secret=os.getenv("GOOGLE_CLIENT_SECRET"),
    server_metadata_url="https://accounts.google.com/.well-known/openid-configuration",
    client_kwargs={"scope": "openid email profile"},
)

Never store OAuth access_token long-term. Store only the user's profile data from the provider after first login.


Common vulnerabilities checklist

VulnerabilityCheck
IDOREvery endpoint with a resource ID verifies ownership
JWT algorithm confusionalgorithms list is pinned, none is impossible
Hardcoded secretsJWT_SECRET, SESSION_SECRET come from env, not code
Weak password hashingbcrypt/argon2 with cost ≥ 10, not SHA-256 or MD5
Token in localStorageTokens in httpOnly cookies, not localStorage
Missing auth on new routesEvery new route explicitly applies auth middleware
Broken logoutLogout deletes the session/refresh token server-side
No rate limiting on loginBrute-force protection on /login and /token endpoints
Verbose error messagesAuth failures return generic messages, not "user not found" vs "wrong password"
Privilege escalationRole changes go through a separate admin action, not a user-editable field

Checklist

  • Passwords hashed with bcrypt or argon2 (cost factor ≥ 10)
  • JWT secret loaded from environment variable, not hardcoded
  • JWT algorithm pinned (algorithms: ["HS256"]), "none" impossible
  • Access tokens short-lived (≤ 15 min), refresh tokens rotated on use
  • Tokens stored in httpOnly, Secure, SameSite=Strict cookies
  • Every protected route has auth middleware applied explicitly
  • Resource ownership verified on every endpoint with an ID parameter (IDOR check)
  • Role-based checks use 403 Forbidden, not 404 (don't leak resource existence to unauthorized users — unless hiding existence is a requirement)
  • Login endpoint has rate limiting
  • Auth errors return generic messages (no "user not found" vs "wrong password")
  • Logout invalidates the token/session server-side
  • No tokens, passwords, or secrets appear in logs

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.