Implement authentication and authorization: sessions, OAuth, tokens, MFA and tenant permissions. Use for login, signup, access control or protected routes.
Install
npx skillscat add nbfrodri/agent-tack/auth Install via the SkillsCat registry.
Authentication and authorisation
Auth is where small mistakes become breaches, so the rule is: don't invent it. Use the framework's or a mature library's implementation, configure it carefully, and test the failure paths as thoroughly as the happy path.
Choosing an approach
| Situation | Recommended |
|---|---|
| Web app where the frontend and API share a domain (Next.js full stack, Laravel + SPA on the same site) | Server-side sessions in an HttpOnly cookie |
| SPA or mobile app talking to your own API | Laravel Sanctum, or short-lived access tokens + rotating refresh tokens |
| Server-to-server or third-party API access | API keys (stored hashed, scoped, revocable) or OAuth2 client credentials |
| "Login with Google/GitHub", or enterprise SSO | OAuth2/OIDC through a library, never hand-rolled |
| You'd rather not run auth yourself | A managed provider (Clerk, Auth0, Supabase Auth, Cognito) |
Prefer sessions unless you need stateless tokens: they're revocable by default and avoid many JWT pitfalls.
Per stack
- Next.js: Auth.js (NextAuth) or Better Auth, or a managed provider (Clerk). Check the session in the data-access layer, Server Actions and Route Handlers, not only in middleware.
- Laravel: starter kits / Breeze / Fortify for flows, Sanctum for SPA and token auth, Policies and Gates for authorisation, and Socialite for OAuth logins.
- Python: Django's built-in auth (+
django-allauthfor social or MFA); for FastAPI, OAuth2 password/bearer flows withpwdlib/argon2-cffiand PyJWT, or a provider such asfastapi-usersor an external IdP. - Node: NestJS with Passport strategies + Guards, or Better Auth / Lucia-style session handling;
argon2for hashing;josefor JWTs.
Non-negotiables
- Passwords: hash with Argon2id (or bcrypt with cost ≥ 12) through the library. Never use MD5/SHA for passwords, and never encrypt them reversibly. Require a minimum length (≥ 8, preferably 12), allow long passphrases, and check them against breached-password lists if possible.
- Cookies:
HttpOnly,Secure,SameSite=Lax(orStrict), a sensible expiry, and session ID rotation on login and privilege change. - CSRF: cookie-based auth needs CSRF protection (framework tokens, or
SameSiteplus Origin checks). Bearer tokens in headers don't. - JWTs (if used): short-lived access tokens (5–15 min); refresh tokens rotated on every use, with reuse detection, stored server-side or in an
HttpOnlycookie, never inlocalStorage. Verify the signature,exp,issandaud, pin the algorithm, and never acceptalg: none. - Rate limiting and lockout on login, signup, password reset and MFA endpoints.
- No user enumeration: login and reset responses are the same whether or not the account exists.
- Reset and verification tokens: random (≥ 128 bits), single-use, short expiry, stored hashed.
- Secrets (signing keys, OAuth client secrets) only in env vars or a secret manager, and rotatable.
- MFA: offer TOTP or passkeys (WebAuthn) for sensitive apps, with recovery codes.
- Logging: log auth events (login success/failure, password change, role change) without passwords, tokens or full session IDs.
Authorisation
- Deny by default; check on the server, on every request, at the resource level ("can this user edit this order?"), not only "is logged in". Hiding a button in the UI is not authorisation.
- Centralise rules in policies or permission functions (
can(user, 'update', order)) instead ofif user.role == 'admin'scattered around. - RBAC (roles → permissions) for most apps; add attribute- or ownership-based checks where resources belong to users or tenants.
- Multi-tenancy: every query is scoped by tenant (a global scope, repository filter or Postgres RLS), and tests prove that tenant A can't read tenant B's data.
- IDOR: never trust IDs from the client to imply ownership.
Testing
For every protected endpoint or page, test: unauthenticated → 401 or redirect, authenticated without permission → 403 (or 404), owner/allowed → success, and another user's resource → denied. Also test expired or tampered tokens, reset-token reuse and rate limiting.