ADR-0002: Authentication and Password Hashing

  • Status: Accepted
  • Date: 2026-09-15
  • Deciders: Backend engineering team

Context

The Conduit API authenticates users and authorizes access to protected resources (current user, article mutations, comments, follows, favorites). The RealWorld API contract expects a bearer token returned on registration and login and supplied via the Authorization: Token <jwt> header. We must also store user passwords securely at rest. Two decisions are required: the session/token mechanism and the password hashing algorithm.

Decision

  • JWT for stateless authentication. On successful registration or login we issue a signed JSON Web Token containing the user id. The token is verified on each protected request by middleware that populates the authenticated user context. Statelessness removes the need for server-side session storage and horizontal-scaling stickiness.
  • argon2 for password hashing. Passwords are hashed with argon2id, the current Password Hashing Competition winner, with sensible memory/time cost parameters.

Alternatives Considered

  • Server-side sessions — require shared session storage and complicate horizontal scaling; rejected in favor of stateless JWTs for this API.
  • bcrypt for hashing — rejected. bcrypt is commonly pulled in via native binding toolchains that historically depend on node-pre-gyp, which has been the subject of a chain of supply-chain and dependency-vulnerability advisories (the node-pre-gyp CVE chain). It also caps effective password length at 72 bytes. argon2id is memory-hard, resists GPU/ASIC cracking better, and avoids that native-toolchain CVE exposure.
  • PBKDF2 / scrypt — acceptable but argon2id is the modern recommended default and offers stronger resistance to parallelized attacks.

Consequences

  • Positive: stateless, horizontally scalable auth; memory-hard hashing with strong resistance to offline cracking; reduced supply-chain risk by avoiding the bcrypt/ node-pre-gyp chain.
  • Negative: JWTs cannot be trivially revoked before expiry, so we keep token lifetime bounded via JWT_EXPIRY; argon2 hashing is intentionally CPU/memory costly.
  • Neutral: the JWT secret and expiry are injected via environment configuration and validated at startup.