itsdangerous
Cryptographically signs data for safe passing to untrusted environments — generates signed tokens with optional expiration. itsdangerous features: Signer for basic HMAC signing, URLSafeSerializer for URL-safe signed JSON (stateless tokens), URLSafeTimedSerializer for timed tokens with expiration, TimestampSigner for signed timestamps, BadSignature exception on invalid signatures, SignatureExpired exception on expired tokens, multiple secret key rotation support (fallback_unsafe_secret_keys), salt parameter for domain separation, and separator customization. Used by Flask for session cookies and password reset tokens.
Score Breakdown
⚙ Agent Friendliness
🔒 Security
HMAC signing using hashlib. Payload is NOT encrypted — only signed. Do not put secrets in payload. SECRET_KEY must be random and at least 32 bytes: secrets.token_hex(32). Use different salts for different token types to prevent token substitution attacks. Timing-safe comparison used internally for signature verification — prevents timing attacks.
⚡ Reliability
Best When
Flask ecosystem token generation (password reset, email verification, CSRF) and stateless signed session data — itsdangerous is the Pallets-ecosystem standard for HMAC-signed tokens.
Avoid When
Standard JWT (use pyjwt), encrypted tokens (use Fernet), large payloads, or when interoperability with other JWT libraries is required.
Use Cases
- • Agent signed token — from itsdangerous import URLSafeTimedSerializer; s = URLSafeTimedSerializer('SECRET_KEY'); token = s.dumps({'user_id': 42, 'action': 'reset'}); payload = s.loads(token, max_age=3600) — timed token; agent generates expiring signed tokens for password reset, email verification; loads() raises SignatureExpired after max_age seconds
- • Agent stateless session — from itsdangerous import URLSafeSerializer; s = URLSafeSerializer('SECRET_KEY', salt='session'); session_token = s.dumps({'user_id': 42, 'role': 'admin'}); data = s.loads(session_token) — URL-safe session; agent creates stateless signed session tokens; no DB required; data is readable but tamper-proof
- • Agent signature verification — from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired; try: data = s.loads(token, max_age=3600) except SignatureExpired: return 'Token expired' except BadSignature: return 'Invalid token' — verification; agent validates tokens with specific error handling for expired vs tampered
- • Agent key rotation — s = URLSafeTimedSerializer('NEW_KEY', fallback_unsafe_secret_keys=['OLD_KEY']); data = s.loads(old_token) — key rotation; agent supports rolling secret key updates; old tokens signed with OLD_KEY still verify; new tokens use NEW_KEY; gradual migration without invalidating existing tokens
- • Agent domain separation — s1 = URLSafeSerializer('SECRET', salt='password-reset'); s2 = URLSafeSerializer('SECRET', salt='email-verify'); token1 = s1.dumps(user_id); s2.loads(token1) — raises BadSignature — salt prevents cross-domain token reuse; agent uses different salts for different token types to prevent substitution attacks
Not For
- • JWT tokens — itsdangerous uses a custom format; for standard JWT use pyjwt or python-jose
- • Encryption — itsdangerous signs but does NOT encrypt; payload is base64-readable; for encrypted tokens use cryptography Fernet
- • Large payloads — URLSafe serializers produce long tokens for large data; for large signed blobs use signed references to DB records
Interface
Authentication
No auth — signing library. Provides building blocks for auth token generation.
Pricing
itsdangerous is BSD 3-Clause licensed. Created by Armin Ronacher (Pallets). Free for all use.
Agent Metadata
Known Gotchas
- ⚠ Payload is readable — URLSafeSerializer encodes with base64 (not encryption); anyone can decode the payload: import base64; base64.b64decode(token.split('.')[0] + '=='); agent code must NOT put sensitive data (passwords, PII) in token payload; token proves authenticity not secrecy; for secrets use Fernet encryption
- ⚠ max_age is in seconds not datetime — s.loads(token, max_age=3600) expires after 3600 seconds (1 hour); agent code with complex expiry: compute seconds: max_age=int(timedelta(days=1).total_seconds()); SignatureExpired has date_signed attribute for when token was created
- ⚠ Same salt required for loads — token generated with salt='reset' must be loaded with salt='reset'; mismatched salt raises BadSignature not helpful error; agent code: use constants for salts to avoid typos: RESET_SALT = 'password-reset'; s.dumps(data, salt=RESET_SALT) / s.loads(token, salt=RESET_SALT)
- ⚠ SECRET_KEY must be kept secret — itsdangerous security depends entirely on SECRET_KEY secrecy; anyone with SECRET_KEY can forge tokens; agent code: load from env var: SECRET_KEY = os.environ['SECRET_KEY']; rotate key periodically; use fallback_unsafe_secret_keys for graceful rotation
- ⚠ dumps() on URLSafeTimedSerializer needs no extra args — s.dumps(payload) works; but s.dumps(payload, salt=...) overrides class salt; agent code: set salt in constructor URLSafeTimedSerializer('key', salt='domain'); do not pass salt to dumps() for consistency; passing salt to dumps() overrides constructor salt
- ⚠ BadSignature is parent of SignatureExpired — catch order matters: except BadSignature catches both; except SignatureExpired catches only expired; agent code: catch in order: try: data = s.loads(token) except SignatureExpired: handle_expired() except BadSignature: handle_tampered() — SignatureExpired must come before BadSignature
Alternatives
Full Evaluation Report
Detailed scoring breakdown, competitive positioning, security analysis, and improvement recommendations for itsdangerous.
Scores are editorial opinions as of 2026-03-06.