A small, opinionated JWT toolkit: create, verify, expire, and handle errors without writing the same PyJWT boilerplate every project.
Project description
fastjwt-kit
A small, opinionated wrapper around PyJWT that
handles the boilerplate every project rewrites: token creation, expiry,
claim validation, and clean error handling, without importing jwt exceptions
directly.
Install
pip install fastjwt-kit
For asymmetric algorithms (RS256, ES256), install the cryptography package as well:
pip install fastjwt-kit cryptography
Quickstart
from fastjwt_kit import create_access_token, verify_token, TokenExpiredError
token = create_access_token(subject="user-123", secret="change-me")
try:
claims = verify_token(token, secret="change-me")
print(claims["sub"]) # "user-123"
except TokenExpiredError:
print("Token expired, ask the user to log in again")
No manual exp math, no catching raw jwt.ExpiredSignatureError.
Token types
create_access_token and create_refresh_token are convenience wrappers that
set sensible defaults and inject a type claim:
from fastjwt_kit import create_access_token, create_refresh_token
access = create_access_token(subject="user-123", secret="change-me") # 15 min, type: "access"
refresh = create_refresh_token(subject="user-123", secret="change-me") # 7 days, type: "refresh"
Use the type claim in the decoded payload to distinguish tokens:
claims = verify_token(access, secret="change-me")
assert claims["type"] == "access"
Creating tokens
create_token
Full control over all claims:
import datetime as dt
from fastjwt_kit import create_token
token = create_token(
subject="user-123",
secret="change-me",
expires_in=dt.timedelta(hours=1),
issuer="my-app",
audience="my-app-clients",
extra_claims={"role": "admin"},
)
| Parameter | Type | Required | Description |
|---|---|---|---|
subject |
str |
Yes | Value of the sub claim, typically a user ID. |
secret |
str |
Yes | Signing secret for HS256, or a private key object/PEM bytes for asymmetric algorithms. |
expires_in |
timedelta |
Yes | How long until the token expires. |
algorithm |
str |
No | Signing algorithm. Defaults to "HS256". |
issuer |
str |
No | Value of the iss claim. |
audience |
str |
No | Value of the aud claim. |
extra_claims |
dict |
No | Additional claims to embed in the payload (for example {"role": "admin"}). |
create_access_token / create_refresh_token
Same parameters as create_token, but expires_in has a built-in default:
| Function | Default expires_in |
Auto-added claim |
|---|---|---|
create_access_token |
15 minutes | type: "access" |
create_refresh_token |
7 days | type: "refresh" |
Verifying tokens
from fastjwt_kit import verify_token
claims = verify_token(
token,
secret="change-me",
issuer="my-app",
audience="my-app-clients",
)
| Parameter | Type | Required | Description |
|---|---|---|---|
token |
str |
Yes | The encoded JWT string. |
secret |
str |
Yes | Signing secret for HS256, or the public key object/PEM bytes for asymmetric algorithms. |
algorithm |
str |
No | Expected signing algorithm. Defaults to "HS256". Must match the algorithm used to sign. |
issuer |
str |
No | If provided, the token's iss claim must match exactly. |
audience |
str |
No | If provided, the token's aud claim must match exactly. |
Returns a dict[str, Any] of the decoded claims on success.
Asymmetric algorithms (RS256, ES256)
For cross-service token exchange or anywhere you cannot share a symmetric secret, use an asymmetric algorithm. Pass key objects directly:
import datetime as dt
from cryptography.hazmat.primitives.asymmetric import rsa
from fastjwt_kit import create_token, verify_token
# Generate a key pair once and store securely
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()
# Sign with the private key
token = create_token(
subject="user-123",
secret=private_key,
expires_in=dt.timedelta(hours=1),
algorithm="RS256",
)
# Verify with the public key
claims = verify_token(token, secret=public_key, algorithm="RS256")
The algorithm value must match in both create_token and verify_token.
Passing a mismatched algorithm to verify_token raises InvalidTokenError.
Error handling
All errors inherit from TokenError:
TokenError
├── TokenExpiredError token's exp claim has passed
├── InvalidTokenError malformed, tampered, or wrong algorithm
└── InvalidClaimsError iss, aud, or other required claims do not match
Catch broadly or specifically:
from fastjwt_kit import TokenError, TokenExpiredError, InvalidTokenError, InvalidClaimsError
try:
claims = verify_token(token, secret="change-me")
except TokenExpiredError:
... # token was valid but has expired
except InvalidClaimsError:
... # issuer or audience did not match
except InvalidTokenError:
... # malformed or tampered token
except TokenError:
... # catch-all for any token error
You never need to import jwt exceptions directly.
API reference
Functions
| Function | Returns | Description |
|---|---|---|
create_token(subject, secret, *, expires_in, ...) |
str |
Create a JWT with full control over all claims. |
create_access_token(subject, secret, ...) |
str |
Short-lived token (default 15 min), tagged type: "access". |
create_refresh_token(subject, secret, ...) |
str |
Long-lived token (default 7 days), tagged type: "refresh". |
verify_token(token, secret, *, algorithm, issuer, audience) |
dict[str, Any] |
Decode and validate a token, raising typed exceptions on failure. |
Exceptions
| Exception | Raised when |
|---|---|
TokenError |
Base class. Catch this to handle any token error. |
TokenExpiredError |
The token's exp claim has passed. |
InvalidTokenError |
The token is malformed, the signature is invalid, or the algorithm does not match. |
InvalidClaimsError |
Required claims (iss, aud) are missing or do not match expected values. |
Why not just use PyJWT directly?
You can. This package wraps the parts everyone ends up rewriting:
- Expiry defaults so you do not hardcode
timedelta(minutes=15)in every project issandaudvalidation plumbed through without managingoptionsdicts manually- Exception types that do not leak the underlying library, so upgrading PyJWT does not break your error handling
If you only need raw encode/decode, PyJWT alone is fine.
Roadmap
- Password hashing module
- FastAPI signup/login router and
get_current_userdependency - Config/env validation helpers
Feedback and issues welcome. This is an early release and will evolve based on real usage.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fastjwt_kit-0.2.0.tar.gz.
File metadata
- Download URL: fastjwt_kit-0.2.0.tar.gz
- Upload date:
- Size: 9.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
407b6ff0db2b0494660bd28e4782b323055accdd77a3fcf3c1c18aef2bb89693
|
|
| MD5 |
68d6ed12bd1a3f5d77fb87d6647e2555
|
|
| BLAKE2b-256 |
896a69da08cc20ba508cfb8d1f858675e43659c32eeb088a458a59937d4d2258
|
File details
Details for the file fastjwt_kit-0.2.0-py3-none-any.whl.
File metadata
- Download URL: fastjwt_kit-0.2.0-py3-none-any.whl
- Upload date:
- Size: 9.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81483e07f975bf159a6c771cf2574e6cd8ccef66d904bb08909f2463de78804b
|
|
| MD5 |
1ade2a87fddfa9dbb0b3b0a07c962f8f
|
|
| BLAKE2b-256 |
171d2f2dd7f84f53f5f24f07e343d9ca790e50fd6bced91b8cb2faf71fd5541c
|