Use Argon2id (or bcrypt $2b$) with a per‑user random salt and store the hash in a dedicated password column, and set session cookies with the Secure, HttpOnly, SameSite=Strict attributes and a short expiration.
1. Hash selection
- Prefer Argon2id (memory‑hard) for new systems; fall back to bcrypt $2b$ if library support is limited.
- Minimum parameters: Argon2id t=2, m=64 MiB, p=2; bcrypt cost ≥ 12.
2. Implementation (Python)
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=2, memory_cost=65536, parallelism=2, hash_len=32, salt_len=16)
hash = ph.hash(user_password)
# Verify
ph.verify(hash, candidate_password)
```
3. **Implementation (Node/Express)**
```javascript
const session = require('express-session');
app.use(session({
secret: crypto.randomBytes(64).toString('hex'),
cookie: {
httpOnly: true,
secure: true, // requires HTTPS
sameSite: 'strict',
maxAge: 15 * 60 * 1000 // 15 min inactivity timeout
},
resave: false,
saveUninitialized: false
}));
```
4. **Storage**
- Store only the hash and its parameters; never keep the plain password or the salt separately.
- Use a CHAR(255) column with binary collation to avoid encoding issues.
5. **Rotation**
- On successful login, check `ph.check_needs_rehash(hash)` (or bcrypt `bcrypt.getRounds`). If true, re‑hash with current parameters and update the DB.
6. **Verification timing**
- Ensure constant‑time comparison; libraries above already provide it.
**Algorithm comparison**Algorithm | Memory (KB) | Time (ms) | Parallelism
Argon2id | 64‑256 | 100‑300 | 2‑4
bcrypt | N/A | 100‑200 | N/A
scrypt | 64‑128 | 150‑350 | 2‑8
```
Gotcha: If your load balancer terminates TLS, add the Secure cookie flag after TLS re‑encryption; otherwise the flag is stripped and the cookie may be sent over HTTP.