🪙 Tokenmint
Tokenmint is a modern Python library for issuing and verifying limited-time, scoped, and replay-protected tokens. Built for 2025 and beyond, it provides a production-grade token system with features like key rotation, purpose scoping, Redis-backed replay prevention, and secure Ed25519 signatures.
✨ Features
- 🔑 Key rotation with
kidheaders for zero-downtime upgrades - ⏳ Limited-time tokens (short-lived, scoped, expiring)
- 🛡 Replay protection using Redis-backed JTI tracking
- 🎯 Strict purpose scoping (e.g.,
email-verify,password-reset) - 🖋 EdDSA (Ed25519) signatures for modern cryptographic security
- 🏗 Production-ready: structured errors, leeway for clock skew, revocation support
- ⚡ Works with Django, FastAPI, Flask, or standalone services
📦 Installation
You can install Tokenmint via pip or uv:
- Using uv
uv add tokenmint
- Using pip
pip install tokenmint
🚀 Quick Start
from datetime import timedelta
from tokenmint.settings import Settings
from tokenmint.services import TokenMint
# Define token settings
settings = Settings(
issuer="myapp.io",
audience="myapp.web",
purpose="email-verify",
expiry_duration=timedelta(minutes=15),
)
# Create token mint (auto-loads keys from environment, uses Redis for replay prevention)
mint = TokenMint(settings)
# Generate a token
token = mint.generate_token(
subject="20",
extra_claims={"email": "alice@example.com"},
)
print("Issued token:", token)
# Validate a token
claims = mint.validate_token(token)
print("Decoded claims:", claims)
🔐 Key Management
tokenmint uses Ed25519 key pairs with support for key rotation. Keys are loaded from environment variables:
export TOKEN_ACTIVE_KEY_ID="2025-08-rot-1"
export TOKEN_PRIVATE_KEY_2025-08-rot-1="$(cat ed25519-private.pem)"
- Rotate by adding a new private key (
TOKEN_PRIVATE_KEY_<new_id>) - Update
TOKEN_ACTIVE_KEY_IDto the new key id - Old tokens remain valid until expiry since their public keys are still available
📖 Detailed Use Cases
tokenmint is designed to fit common security-sensitive flows. Below are example implementations.
1. 🔑 Email Verification
Send a time-limited verification link when a user signs up:
from datetime import timedelta
from tokenmint.settings import Settings
from tokenmint.services import TokenMint
settings = Settings(
issuer="myapp.io",
audience="myapp.web",
purpose="email-verify",
expiry_duration=timedelta(minutes=10),
)
mint = TokenMint(settings)
# Issue a token for user
token = mint.generate_token(
subject="20",
extra_claims={"email": "alice@example.com"},
)
# Send via email link
verification_url = f"https://myapp.io/verify-email?token={token}"
print("Verification URL:", verification_url)
# Later, when user clicks the link
claims = mint.validate_token(token)
print("Verified email:", claims["email"])
✔ Prevents token replay
✔ Short lifetime
✔ Scoped to "email-verify"
2. 🔒 Password Reset
settings = Settings(
issuer="myapp.io",
audience="myapp.web",
purpose="password-reset",
expiry_duration=timedelta(minutes=5),
)
mint = TokenMint(settings)
# Issue reset token
reset_token = mint.generate_token(subject="20")
# User submits new password with token
claims = mint.validate_token(reset_token)
# Force revoke once used
mint.revoke_token(reset_token)
✔ Ensures token can’t be reused ✔ Scoped only for password reset flow
3. 📩 Magic Login Links
No passwords — just a one-time-use link:
settings = Settings(
issuer="myapp.io",
audience="myapp.web",
purpose="magic-login",
expiry_duration=timedelta(minutes=2),
)
mint = TokenMint(settings)
login_token = mint.generate_token(subject="20")
# When clicked
claims = mint.validate_token(login_token)
print("User logged in:", claims["sub"])
✔ Very short-lived ✔ One-time login enforcement via replay cache
4. ⚙️ Scoped API Access
Issue short-lived tokens for microservice-to-microservice communication:
settings = Settings(
issuer="auth.myapp.io",
audience="payments.myapp.io",
purpose="service-access",
expiry_duration=timedelta(minutes=1),
)
mint = TokenMint(settings)
api_token = mint.generate_token(subject="user@gmail.com")
# Receiving service validates
claims = mint.validate_token(api_token)
print("Authorized service:", claims["sub"])
✔ Audience restriction ✔ Purpose scoping ensures the token is useless elsewhere
5. 🎟 One-Time Session Tokens
Great for single-use sensitive operations (like transferring funds):
settings = Settings(
issuer="myapp.io",
audience="myapp.api",
purpose="txn-approval",
expiry_duration=timedelta(minutes=3),
prevent_replay=True,
)
mint = TokenMint(settings)
txn_token = mint.generate_token(
subject="20",
extra_claims={"amount": "100.00", "currency": "USD"},
)
# Validate (once only)
claims = mint.validate_token(txn_token)
# Reuse attempt → raises InvalidTokenError
mint.validate_token(txn_token)
6. 🏗 One-Time Payment Approval Token
Scenario: Your app allows users to approve payments securely. Each token must:
- Be valid only for a single payment request
- Expire quickly (e.g., 3 minutes)
- Include extra claims like
amountandcurrency - Prevent token replay using Redis
- Support production-grade key rotation
Environment Setup
export TOKEN_ACTIVE_KEY_ID="2025-08-rot-1"
export TOKEN_PRIVATE_KEY_2025-08-rot-1="$(cat prod-ed25519-rot1.pem)"
export TOKEN_PRIVATE_KEY_2025-05-rot-0="$(cat prod-ed25519-rot0.pem)"
export REDIS_URL="redis://prod-redis-server:6379/3"
Define Token Settings
from datetime import timedelta
from tokenmint.settings import Settings
settings = Settings(
issuer="payments.myapp.io",
audience="payments.api",
purpose="payment-approval", # scoped to payment approval
expiry_duration=timedelta(minutes=3),
clock_skew_leeway=5, # allow 5 seconds for clock differences
prevent_replay=True, # single-use enforcement
)
Configure Redis Replay Cache
from tokenmint.cache import ReplayCache
import os
redis_url = os.getenv("REDIS_URL")
replay_cache = ReplayCache(redis_url=redis_url, key_prefix="prod:payment:jti:")
✅ This isolates payment tokens in Redis to prevent collisions with other token types.
Initialize TokenMint
from tokenmint.services import TokenMint
mint = TokenMint(
settings=settings,
replay_cache=replay_cache,
)
Generate Payment Approval Token
payment_token = mint.generate_token(
subject="20",
extra_claims={"amount": "250.00", "currency": "USD", "payment_id": "txn_1001"}
)
print("Send this token to the frontend for approval:", payment_token)
✅ Includes amount, currency, and payment_id claims, ensuring the token is specific to one payment.
Validate Token During Approval
from tokenmint.exceptions import InvalidTokenError
try:
claims = mint.validate_token(payment_token)
print("Payment approved for:", claims["sub"])
print("Amount:", claims["amount"], claims["currency"])
except InvalidTokenError as e:
print("Invalid or reused token:", str(e))
✅ Checks signature, issuer, audience, expiry, and prevents reuse.
Revoke Token Explicitly (Optional)
mint.revoke_token(payment_token)
✅ Ensures a token can never be reused even before natural expiry.
Production Notes
- Use dedicated Redis DBs or key prefixes for different token types (
prod:payment:jti:). - Rotate keys safely with old keys kept in
KeyStorefor verification of existing tokens. - ReplayCache TTL matches token expiry, no manual cleanup needed.
- Purpose scoping (
payment-approval) prevents tokens from being misused for other actions.
🔑 Build Any Tokenized Flow with Replay Protection
tokenmint enforces true single-use tokens with Redis-backed replay prevention.
With these building blocks, you can implement any tokenized flow - from user onboarding to secure inter-service communication.
🤝 Contributing
Contributions are welcome! Please open an issue or PR for any improvements.
📜 License
MIT License — See LICENSE.
👤 Author
Shailesh Pandit
- 🌐 Website: https://github.com/shaileshpandit141
- 🐙 GitHub: https://github.com/shaileshpandit141
- 💼 LinkedIn: https://www.linkedin.com/in/shaileshpandit141
- ✉️ Email: shaileshpandit141@gmail.com
Metadata
Release files for tokenmint 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tokenmint-0.1.2.tar.gz | 21.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tokenmint-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.7 kB
Release files / tokenmint-0.1.2.tar.gz
| Download URL | tokenmint-0.1.2.tar.gz |
|---|---|
| Size | 21.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d03bb89fdfb6f9f8cae4a7f49cde87ab603667c1652e864e68905f931b9c9410
|
|
BLAKE2b-256 checksum How to use checksums |
167812dc9459617a67a2df8c839cc2a7ed13b9a685011e9749564fe735df3852
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.6
|
Release files / tokenmint-0.1.2-py3-none-any.whl
| Download URL | tokenmint-0.1.2-py3-none-any.whl |
|---|---|
| Size | 13.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8ef6e1a200f32c934485b7bec5bb5e45f328ad98a39feb9965b7e418e7c4305b
|
|
BLAKE2b-256 checksum How to use checksums |
3841b911bcefa5b6d911981951bd2b386dc58a2017b75cef75bbd2c678cbbfe4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.6
|