python 42 lines · 7 steps

Building signed expiring tokens with HMAC

A minimal JWT-style scheme that signs a JSON payload with HMAC-SHA256 and verifies it in constant time.

Explained by highlit
1import base64
2import hashlib
3import hmac
4import json
5import time
6 
7 
8class TokenError(Exception):
9 pass
10 
11 
12def _b64encode(raw: bytes) -> str:
13 return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
14 
15 
16def _b64decode(data: str) -> bytes:
17 padding = "=" * (-len(data) % 4)
18 return base64.urlsafe_b64decode(data + padding)
19 
20 
21def issue_token(payload: dict, secret: str, ttl: int = 3600) -> str:
22 body = dict(payload, exp=int(time.time()) + ttl)
23 encoded = _b64encode(json.dumps(body, separators=(",", ":")).encode())
24 signature = hmac.new(secret.encode(), encoded.encode(), hashlib.sha256).digest()
25 return f"{encoded}.{_b64encode(signature)}"
26 
27 
28def verify_token(token: str, secret: str) -> dict:
29 try:
30 encoded, provided = token.split(".", 1)
31 except ValueError:
32 raise TokenError("malformed token")
33 
34 expected = hmac.new(secret.encode(), encoded.encode(), hashlib.sha256).digest()
35 if not hmac.compare_digest(expected, _b64decode(provided)):
36 raise TokenError("invalid signature")
37 
38 payload = json.loads(_b64decode(encoded))
39 if payload.get("exp", 0) < int(time.time()):
40 raise TokenError("token expired")
41 
42 return payload
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Signing a payload with a shared secret lets you trust its contents without storing server-side state.
  2. 2Always compare signatures with a constant-time function to avoid leaking bytes through timing.
  3. 3Baking an expiry into the signed body means a stolen token stops working on its own.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building signed expiring tokens with HMAC — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code