Claude Fable 5.1 & GPT-6 Astra packages are live

Authentication

Free

Password storage, session handling and login-flow rules for building authentication that survives a credential-stuffing campaign and a database leak.

231 lines9.0 KB Sarvam Ai Security
targetModels
Sarvam-105BSarvam-30BSarvam FamilyFuture Sarvam Models
name
authentication
category
Security
description
Password storage, session handling and login-flow rules for building authentication that survives a credential-stuffing campaign and a database leak.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Sarvam: scripts/model-profiles.json -->

#Locale

Examples use Indian conventions: ₹ amounts, IST, dd/mm/yyyy, Aadhaar and DPDP Act where a standard mentions identity or privacy law. Keep them when you copy an example.


#Purpose

Rules for implementing authentication: how to store credentials, how to issue and end sessions, and how to fail safely. Scope is proving who a user is. Deciding what they may then do is Security/authorization.

Assume the database will leak. Every rule here is chosen so that a full dump of your users table does not hand an attacker working credentials.


#Password storage

#Use a memory-hard KDF. Never a general-purpose hash.

Correct, in order of preference:

AlgorithmParametersNotes
argon2idm=19456 (19 MiB), t=2, p=1Default choice. OWASP-recommended baseline.
scryptN=2^17, r=8, p=1Use when argon2id is unavailable.
bcryptcost=12 minimumAcceptable. Truncates at 72 bytes — pre-hash longer inputs.
PBKDF2-HMAC-SHA256600000 iterationsOnly when FIPS compliance forces it.
js
// Node — argon2id with explicit parameters, never the library defaults alone.
import argon2 from "argon2";

const hash = await argon2.hash(password, {
  type: argon2.argon2id,
  memoryCost: 19456, // KiB
  timeCost: 2,
  parallelism: 1,
});

Never use md5, sha1, sha256, or any bare digest for passwords. They are designed to be fast, which is the opposite of what is required. A commodity GPU tries billions of SHA-256 candidates per second.

Never implement your own salting scheme. argon2id, scrypt and bcrypt generate and embed a per-password salt in the output string. A separate salt column is a sign the KDF is being misused.

Never apply a "pepper" stored in the same database as the hashes. If it is in the dump, it is not a secret.

#Verify in constant time

Use the library's own verifier — argon2.verify(), bcrypt.compare(). Never compare hashes with === or ==. For any other secret comparison (API keys, tokens) use crypto.timingSafeEqual.

#Rehash on login when parameters change

Store the full encoded hash string ($argon2id$v=19$m=19456,t=2,p=1$...), which carries its own parameters. On successful login, if the stored parameters are weaker than current policy, rehash the plaintext you already have in memory and update the row. This is the only moment the plaintext is available.


#Password policy

  • Minimum 8 characters. Maximum at least 64. A low maximum is a strong signal the password is being stored in a fixed-width column, unhashed.
  • Accept every Unicode character, including spaces and emoji. Normalise to NFKC before hashing so the same typed password verifies across platforms.
  • Check against a breach corpus (Have I Been Pwned range API, or a local copy). Rejecting known-breached passwords prevents more account takeover than any composition rule.
  • No composition rules. Do not require a symbol, a digit and mixed case. They push users toward Password1! and provide no measurable benefit.
  • No forced rotation on a schedule. Rotate on evidence of compromise only.

#Sessions

#Prefer opaque server-side sessions

A random session identifier in a cookie, with state held server-side, is the default. It can be revoked instantly. Use JWT only when statelessness is a real requirement — and then read Security/jwt for its failure modes.

ini
Set-Cookie: sid=<128-bit random>; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1209600

Every attribute above is load-bearing:

AttributePrevents
HttpOnlyToken theft via XSS — JavaScript cannot read the cookie
SecureTransmission over plaintext HTTP
SameSite=LaxMost CSRF, while keeping top-level navigation logins working
Path=/Scope confusion across sub-applications

Never store a session token in localStorage. It is readable by any script on the page, which converts any XSS into full account takeover. This is the most common authentication mistake in single-page applications.

Generate identifiers with a CSPRNG — crypto.randomBytes(32), not Math.random(), not a timestamp, not a UUIDv1 (which encodes MAC and time).

#Rotate on privilege change

Issue a new session identifier on login, on logout, and on any privilege elevation. Reusing the pre-login identifier is session fixation: an attacker who plants a known identifier before login holds a valid session after it.

#Expire on two clocks

Enforce both an idle timeout and an absolute lifetime. Idle timeout alone lets a stolen token live indefinitely under automated use.

#Logout must destroy server-side state

Clearing the cookie is not logout. Delete the session record. Otherwise a captured token remains valid until natural expiry.


#Login flow

#Fail identically for every cause

sql
# Correct — one message, one status, one timing profile
401  "Invalid email or password."

Never distinguish "no such user" from "wrong password", in the body, the status code, or the response time. Any difference is a user-enumeration oracle. Where the code paths differ in cost, perform a dummy KDF verification against a fixed hash so both branches take comparable time.

Apply the same rule to password reset and signup: "If that address exists, we have sent a link" — always, regardless.

#Rate limit on two keys

Limit per-account and per-IP independently. Per-IP alone does not stop a distributed credential-stuffing run against one account; per-account alone lets one IP spray many accounts.

Prefer exponential backoff or a temporary lock over a permanent one — a permanent lock triggered by failed attempts is a denial-of-service primitive against your own users.

#Multi-factor

Offer TOTP (RFC 6238) or WebAuthn. Prefer WebAuthn — it is phishing-resistant because the credential is bound to the origin.

  • SMS is a weak factor (SIM swap). Offer it only as a fallback, never as the only option.
  • Verify TOTP against a ±1 step window, no wider.
  • Burn each TOTP code once. Without single-use enforcement, a code is replayable for its full validity window.
  • Generate single-use recovery codes at enrolment and hash them like passwords.

#Password reset

  • Tokens must be single-use, short-lived (≤ 60 minutes), and CSPRNG-generated.
  • Store the hash of the reset token, not the token. A leaked database must not yield working reset links.
  • Invalidate all existing sessions on password change, except optionally the one performing the change.
  • Never send the new or existing password by email.

#Anti-patterns

Anti-patternWhy it failsFix
sha256(password + salt)GPU-fast; billions of guesses per secondargon2id
Session token in localStorageAny XSS becomes account takeoverHttpOnly cookie
"User not found" vs "wrong password"User-enumeration oracleOne identical failure response
Reusing the session id after loginSession fixationRotate on every privilege change
Reset token stored in plaintextDB leak yields working reset linksStore its hash
Math.random() for tokensPredictable; not a CSPRNGcrypto.randomBytes(32)
Max password length of 16Implies storage, not hashingAccept ≥ 64 characters
Forced 90-day rotationDrives predictable incrementsRotate on compromise only

#Checklist

  • Verify: Passwords hashed with argon2id (m=19456, t=2, p=1) or an approved alternative
  • Verify: No bare md5 / sha1 / sha256 anywhere in the credential path
  • Verify: Verification uses the library comparator, never ===
  • Verify: Hashes upgraded on login when parameters are below policy
  • Verify: Maximum password length ≥ 64; all Unicode accepted; NFKC normalised
  • Verify: Candidate passwords checked against a breach corpus
  • Verify: Session cookie carries HttpOnly, Secure, SameSite
  • Verify: No session token in localStorage or sessionStorage
  • Verify: Session identifier rotated on login, logout and privilege change
  • Verify: Both idle and absolute session expiry enforced
  • Verify: Logout deletes server-side session state
  • Verify: Login, signup and reset return identical responses for unknown accounts
  • Verify: Rate limiting keyed on both account and IP
  • Verify: Reset tokens single-use, ≤ 60 minutes, stored hashed
  • Verify: All sessions invalidated on password change
  • Verify: MFA available; WebAuthn preferred; TOTP codes single-use