Your SaaS auth checklist: 20 things that should already be true

The pragmatic list of auth controls every production SaaS needs. If you can't tick all 20, you have work to do.

· LoginWith team

Most teams ship auth in a rush and then discover gaps quarterly — during an incident, a compliance review, or a customer’s security questionnaire. Here’s the checklist of things that should already be true in production. Each one is cheap individually; skipping them compounds.

Green checkmarks are what you want. Missing any? Fix before you forget.

Credentials & secrets

  • Passwords are hashed with Argon2id (or bcrypt at work factor ≥ 10 for legacy). Never SHA-256 alone, never MD5.
  • Session tokens are hashed at rest. A DB dump reveals no live credentials.
  • OAuth client secrets live in a secret manager, not in the repo, not in environment files checked into git.
  • Signing keys rotate on a schedule, with a documented procedure. Old keys stay available for verification during overlap, not forever.

Sign-in hardening

  • Rate-limited by account, not just by IP. 5 failed attempts in 5 min → 15-min cooldown, escalating on repeat.
  • Password reset tokens expire in 30 min or less and are single-use.
  • Magic link tokens expire in 15 min or less and are single-use.
  • MFA endpoints rate-limited separately from login. Guessing a 6-digit code is a real attack vector.
  • Session IDs rotate on every privilege change (login, MFA, password change, impersonation).

Cookies

  • All session cookies are HttpOnly; Secure; SameSite=Lax (or SameSite=None; Secure; Partitioned for cross-site contexts).
  • No session tokens in localStorage. localStorage is XSS-readable; cookies with HttpOnly are not.

OAuth correctness

  • PKCE is used on every authorization flow. Required for public clients, recommended for all.
  • The state parameter is generated, stored, and verified on every callback.
  • iss, aud, and exp are validated on every JWT. Always pin the algorithm; never accept alg: none.
  • JWKS is cached with a reasonable TTL (1 hour typical) and refetched on kid miss.

Multi-tenant isolation

  • Every table with tenant-scoped data has a tenant_id column. Every query filters by it.
  • Tenant is derived from the session, never from client input. No ?tenantId= query params trusted.
  • Postgres row-level security is enabled (or equivalent enforcement) as defense in depth.

Enterprise-readiness

  • User model has an auth_provider column distinguishing local, OAuth, and SAML/SCIM users.
  • SSO users can’t change their own email — the IdP owns that field.
  • Audit logs exist for auth events (login, logout, MFA, password change, role change, impersonation), stored immutably.

How you’d know

For each of these: can you point to the code, the config, or the test that proves it’s true? If “we think so but haven’t checked,” that’s a gap.

Running this checklist against a production system usually surfaces 3-5 misses, even on well-regarded auth implementations. Fix them now, not during the next audit.

The shortcut

If you’re building auth from scratch, each checklist item is engineering work. If you’re on LoginWith, most of them — PKCE, JWKS rotation, JWT validation, rate limiting, session rotation, audit logging — are the product’s default behavior. You configure; we enforce. That’s a large chunk of the checklist ticked by picking the right platform.

For everything tenant- and app-specific (multi-tenant isolation, auth_provider routing, audit log content), the responsibility stays yours — but the protocol-level work is handled for you.

Save this page. Run through it quarterly. The three minutes it takes to tick 20 boxes is the cheapest security hygiene you can do.

Want auth that just works?

Get started with LoginWith