php 58 lines · 8 steps

Signed remember-me cookies in PHP

A tamper-proof persistent-login cookie built from an HMAC-signed, base64-encoded payload.

Explained by highlit
1<?php
2 
3final class RememberMeCookie
4{
5 private const COOKIE_NAME = 'remember_me';
6 private const TTL = 60 * 60 * 24 * 30;
7 
8 public function __construct(private readonly string $secret)
9 {
10 }
11 
12 public function issue(int $userId, string $token): void
13 {
14 $expires = time() + self::TTL;
15 $payload = base64_encode(json_encode([
16 'uid' => $userId,
17 'tok' => $token,
18 'exp' => $expires,
19 ], JSON_THROW_ON_ERROR));
20 
21 $value = $payload . '.' . $this->sign($payload);
22 
23 setcookie(self::COOKIE_NAME, $value, [
24 'expires' => $expires,
25 'path' => '/',
26 'secure' => true,
27 'httponly' => true,
28 'samesite' => 'Lax',
29 ]);
30 }
31 
32 public function verify(): ?array
33 {
34 $raw = $_COOKIE[self::COOKIE_NAME] ?? '';
35 if (!str_contains($raw, '.')) {
36 return null;
37 }
38 
39 [$payload, $signature] = explode('.', $raw, 2);
40 $expected = $this->sign($payload);
41 
42 if (!hash_equals($expected, $signature)) {
43 return null;
44 }
45 
46 $data = json_decode((string) base64_decode($payload, true), true);
47 if (!is_array($data) || ($data['exp'] ?? 0) < time()) {
48 return null;
49 }
50 
51 return ['user_id' => (int) $data['uid'], 'token' => (string) $data['tok']];
52 }
53 
54 private function sign(string $payload): string
55 {
56 return hash_hmac('sha256', $payload, $this->secret);
57 }
58}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Signing a payload with HMAC lets you trust client-stored data without a server-side session.
  2. 2Always compare signatures with a constant-time function like hash_equals to avoid timing attacks.
  3. 3Embed and re-check an expiry inside the payload so old cookies can't be replayed forever.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Signed remember-me cookies in PHP — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code