ryjwt¶
ryjwt is a Rust-powered, AI-implemented JWT library for Python (a note on AI, and the motivation behind ryjwt).
It signs and verifies JWTs with HMAC, RSA, RSA-PSS, ECDSA and EdDSA keys, checks exp, nbf,
aud and iss, and decodes the claims into a dict, msgspec
Struct or pydantic BaseModel. It also fetches, caches and
refreshes keys from JWKS URLs, and publishes your own keys as a JWKS.
Install¶
Python 3.12 to 3.15, including free-threaded 3.14t and 3.15t (threads). No runtime dependencies. Wheels for Linux (glibc and musl, x86_64 and aarch64), macOS (x86_64 and arm64) and Windows (x64).
Extras: ryjwt[msgspec] speeds up dict decoding and adds Structs; ryjwt[pydantic] installs
pydantic, for models.
A first token¶
Pick a tab; the rest of the site follows it. Copy for uv copies a command that runs the example, extra and all, with nothing installed first.
import secrets
from datetime import UTC, datetime, timedelta
import msgspec
import ryjwt
class Claims(msgspec.Struct):
sub: str
aud: str
exp: datetime
key = ryjwt.SecretKey(secrets.token_bytes(32), algorithms=["HS256"], audience="my-api")
# Tokens store exp in whole seconds, so round it for the round trip to compare equal.
expires = datetime.now(UTC).replace(microsecond=0) + timedelta(minutes=15)
claims_in = Claims(sub="user-1", aud="my-api", exp=expires)
token = key.encode(claims_in)
claims_out = key.decode(token, into=Claims) # a Claims: signature, exp and aud checked
assert claims_in == claims_out
import secrets
from datetime import UTC, datetime, timedelta
import pydantic
import ryjwt
class Claims(pydantic.BaseModel):
sub: str
aud: str
exp: datetime
key = ryjwt.SecretKey(secrets.token_bytes(32), algorithms=["HS256"], audience="my-api")
# Tokens store exp in whole seconds, so round it for the round trip to compare equal.
expires = datetime.now(UTC).replace(microsecond=0) + timedelta(minutes=15)
claims_in = Claims(sub="user-1", aud="my-api", exp=expires)
token = key.encode(claims_in)
claims_out = key.decode(token, into=Claims) # a Claims: signature, exp and aud checked
assert claims_in == claims_out
Getting started goes on from there.
Why it's fast¶
One bar per library, decoding 100,000 HS256 tokens in real time (benchmarks).
- The work happens in Rust: splitting and decoding the token, and checking the signature with aws-lc-rs.
- Keys are prepared once, when you create the key object.
- Repeat headers are free. Tokens from one issuer share a header. Once one verifies, ryjwt remembers which key it picks and skips parsing it. The signature and claims are still checked every time.
- Typed claims skip the dict. With
into=, your class is built straight from the payload. Dicts are built by msgspec if installed, otherwise by the built-in jiter.
With RSA, ECDSA and EdDSA, most of the time goes on the signature check, so the gaps are smaller. ryjwt is about twice as fast as PyJWT with ES256. With RS256 it's 1.7 to 2.9 times as fast: the fastest of them on x86_64, and second to fast-jwt (Bun) on Arm (benchmarks).