itsdangerous URL-safe timed tokens: working versions (tested 2026-10-16)
Sign URL-safe, expiring email-confirmation and password-reset tokens with salts and max_age, testing expiry and salt separation offline with freezegun.
passed6 passed · 0 failed2026-10-16 21:56 UTC- Status
- passed
- Last passed
- 2026-10-16 21:56 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38972656245
The code
Exactly the files that passed. Your agent can pull them with get_recipe("itsdangerous-urlsafe-timed-tokens") (install), or see them on GitHub.
itsdangerous==2.2.0 freezegun==1.5.5 pytest==9.1.1
import os
from itsdangerous import BadSignature, URLSafeTimedSerializer
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-only-secret")
EMAIL_SALT = "email-confirm-v1"
RESET_SALT = "password-reset-v1"
EMAIL_MAX_AGE = 60 * 60 * 24
RESET_MAX_AGE = 60 * 30
def _serializer(salt: str) -> URLSafeTimedSerializer:
return URLSafeTimedSerializer(secret_key=SECRET_KEY, salt=salt)
def make_email_token(user_id: int) -> str:
return _serializer(EMAIL_SALT).dumps({"uid": user_id})
def confirm_email(token: str, max_age: int = EMAIL_MAX_AGE) -> int:
data = _serializer(EMAIL_SALT).loads(token, max_age=max_age)
return data["uid"]
def make_reset_token(user_id: int, password_hash: str) -> str:
return _serializer(RESET_SALT).dumps({"uid": user_id, "ph": password_hash})
def load_reset_token(token: str, password_hash: str, max_age: int = RESET_MAX_AGE) -> int:
data = _serializer(RESET_SALT).loads(token, max_age=max_age)
if data["ph"] != password_hash:
raise BadSignature("token was issued against an older password")
return data["uid"]
import re
import pytest
from freezegun import freeze_time
from itsdangerous import BadSignature, SignatureExpired
import tokens
def test_roundtrip_within_max_age():
with freeze_time("2026-01-01 00:00:00"):
token = tokens.make_email_token(42)
with freeze_time("2026-01-01 00:10:00"):
assert tokens.confirm_email(token) == 42
def test_expiry_raises_and_is_a_bad_signature():
with freeze_time("2026-01-01 00:00:00"):
token = tokens.make_email_token(42)
with freeze_time("2026-01-03 00:00:00"):
with pytest.raises(SignatureExpired):
tokens.confirm_email(token)
# gotcha: SignatureExpired subclasses BadSignature, so catch it first
assert issubclass(SignatureExpired, BadSignature)
def test_salts_keep_contexts_separate():
token = tokens.make_email_token(42)
with pytest.raises(BadSignature):
tokens.load_reset_token(token, "unused-hash")
def test_token_is_url_safe():
token = tokens.make_email_token(42)
assert re.fullmatch(r"[A-Za-z0-9._-]+", token)
assert not set(token) & set("+/=")
assert "/reset?token=" + token
def test_reset_token_dies_when_password_changes():
token = tokens.make_reset_token(7, "old-hash")
assert tokens.load_reset_token(token, "old-hash") == 7
with pytest.raises(BadSignature):
tokens.load_reset_token(token, "new-hash")
def test_tampered_token_is_rejected():
token = tokens.make_email_token(42)
with pytest.raises(BadSignature):
tokens.confirm_email(token[:-1] + ("A" if token[-1] != "A" else "B"))
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Click CLI for RRULE next run times, tested with freezegun and CliRunner
A Click command that computes the next run times of an RFC 5545 rule in any IANA zone with dateutil rrule and zoneinfo, with DST gaps normalized and time frozen with freezegun in tests.
pyotp TOTP and HOTP codes tested at frozen time
Generate RFC-compatible TOTP and HOTP codes, build otpauth provisioning URIs and tolerate one-step clock drift, all pinned to deterministic time with freezegun.
Starlette SessionMiddleware signed cookies with itsdangerous
Add cookie sessions to a Starlette app via SessionMiddleware, then test login, tampering, logout, expiry and JSON safety fully offline over httpx ASGITransport.