php 46 lines · 8 steps

Secure password hashing with Argon2id in PHP

A service that hashes passwords with Argon2id and verifies logins while resisting timing attacks and transparently upgrading old hashes.

Explained by highlit
1<?php
2 
3namespace App\Security;
4 
5use App\Repository\UserRepository;
6 
7final class PasswordService
8{
9 private const ALGO = PASSWORD_ARGON2ID;
10 
11 private const OPTIONS = [
12 'memory_cost' => 65536,
13 'time_cost' => 4,
14 'threads' => 2,
15 ];
16 
17 public function __construct(private UserRepository $users)
18 {
19 }
20 
21 public function hash(string $plain): string
22 {
23 return password_hash($plain, self::ALGO, self::OPTIONS);
24 }
25 
26 public function attempt(string $email, string $plain): ?User
27 {
28 $user = $this->users->findByEmail($email);
29 
30 if ($user === null) {
31 password_verify($plain, '$argon2id$dummyhashtopreventtimingleaks');
32 return null;
33 }
34 
35 if (!password_verify($plain, $user->getPasswordHash())) {
36 return null;
37 }
38 
39 if (password_needs_rehash($user->getPasswordHash(), self::ALGO, self::OPTIONS)) {
40 $user->setPasswordHash($this->hash($plain));
41 $this->users->save($user);
42 }
43 
44 return $user;
45 }
46}
01 / 01
STEP 01

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1Centralizing the hash algorithm and its cost parameters as constants keeps hashing and verification consistent across the app.
  2. 2Running a dummy verification when no user is found keeps response time uniform, hiding whether an email exists.
  3. 3Checking password_needs_rehash on each successful login lets you raise security parameters over time without forcing resets.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Secure password hashing with Argon2id in PHP — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code