Metadata-Version: 2.4
Name: ryjwt
Version: 0.1.2
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Rust
Classifier: Topic :: Security
Classifier: Topic :: Security :: Cryptography
Classifier: Typing :: Typed
Requires-Dist: msgspec>=0.22 ; extra == 'msgspec'
Requires-Dist: pydantic>=2.12 ; extra == 'pydantic'
Provides-Extra: msgspec
Provides-Extra: pydantic
License-File: LICENSE
License-File: THIRD_PARTY_LICENSES
Summary: Fast, strictly typed JWTs for Python, written in Rust: HMAC, RSA, ECDSA and EdDSA, msgspec and pydantic claims, JWKS URLs
Keywords: jwt,jws,jwks,jose,token,authentication,msgspec,pydantic,rust
Author-email: Roger Thomas <roger.thomas87@gmail.com>
License-Expression: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/RogerThomas/ryjwt
Project-URL: Issues, https://github.com/RogerThomas/ryjwt/issues
Project-URL: Repository, https://github.com/RogerThomas/ryjwt

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/RogerThomas/ryjwt/main/assets/logo-dark.svg">
    <img src="https://raw.githubusercontent.com/RogerThomas/ryjwt/main/assets/logo-light.svg" alt="ryjwt" width="300">
  </picture>
</p>

# ryjwt

[![PyPI](https://img.shields.io/pypi/v/ryjwt)](https://pypi.org/project/ryjwt/)
[![CI](https://github.com/RogerThomas/ryjwt/actions/workflows/ci.yml/badge.svg)](https://github.com/RogerThomas/ryjwt/actions/workflows/ci.yml)
[![Python versions](https://img.shields.io/pypi/pyversions/ryjwt)](https://pypi.org/project/ryjwt/)
[![License](https://img.shields.io/pypi/l/ryjwt)](https://github.com/RogerThomas/ryjwt/blob/main/LICENSE)
[![Docs](https://img.shields.io/badge/docs-rogerthomas.github.io%2Fryjwt-blue)](https://rogerthomas.github.io/ryjwt/)

Fast, strictly typed JSON Web Tokens for Python, written in Rust.

**Documentation: [rogerthomas.github.io/ryjwt](https://rogerthomas.github.io/ryjwt/)**

It signs and verifies JWTs, checks their claims, and decodes them into a dict, msgspec `Struct` or
pydantic `BaseModel`. It also fetches and caches keys from JWKS URLs, and publishes yours as a
JWKS.

![A race to decode 100,000 HS256 tokens: ryjwt finishes in about 0.2 seconds, jsonwebtoken (Rust) in about 0.4 to 0.5, fast-jwt (Bun) in 0.7, and joserfc, jose and PyJWT in about 3 to 6 seconds](https://raw.githubusercontent.com/RogerThomas/ryjwt/main/assets/perf-race.svg)

One bar per library, decoding 100,000 HS256 tokens in real time
([benchmarks](https://rogerthomas.github.io/ryjwt/benchmarks/)).

## Install

```console
uv add ryjwt
```

Python 3.12 to 3.14, including free-threaded 3.14t.

## Example

```python
import secrets
import time

import ryjwt

key = ryjwt.SecretKey(secrets.token_bytes(32), algorithms=["HS256"], audience="my-api")

token = key.encode({"sub": "user-1", "aud": "my-api", "exp": int(time.time()) + 900})
claims = key.decode(token)  # signature, exp and aud checked
```

Into a `Struct`, with `uv add 'ryjwt[msgspec]'`:

```python
import secrets
from datetime import UTC, datetime, timedelta

import msgspec
import ryjwt


class Claims(msgspec.Struct):
    sub: str
    exp: datetime


key = ryjwt.SecretKey(secrets.token_bytes(32), algorithms=["HS256"])

# 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", exp=expires)

token = key.encode(claims_in)
claims_out = key.decode(token, type=Claims)  # a Claims, with exp checked
assert claims_in == claims_out
```

Into a pydantic `BaseModel`, with `uv add 'ryjwt[pydantic]'`:

```python
import secrets
from datetime import UTC, datetime, timedelta

import pydantic
import ryjwt


class Claims(pydantic.BaseModel):
    sub: str
    exp: datetime


key = ryjwt.SecretKey(secrets.token_bytes(32), algorithms=["HS256"])

# 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", exp=expires)

token = key.encode(claims_in)
claims_out = key.decode(token, type=Claims)  # a Claims, with exp checked
assert claims_in == claims_out
```

## Highlights

- **Fast:** over 28 times faster than PyJWT 2.15 on a typical HS256 token, on one core.
- **Every common algorithm:** HMAC, RSA, RSA-PSS, ECDSA and Ed25519 (`HS*`, `RS*`, `PS*`, `ES*`,
  `EdDSA`).
- **Typed:** Literal algorithm names, and `decode(token, type=Claims)` returns a `Claims`.
- **Safe defaults:** keys accept only the algorithms you give them. Weak keys, short secrets and
  malformed tokens are rejected.
- **JWKS URLs:** `JWKSClient` keeps a provider's keys current, from sync or async code, through
  outages.
- **Publish your keys:** `ryjwt.jwks([key])` gives the JWKS to serve at `/.well-known/jwks.json`.
- **Free-threaded:** objects are thread-safe, and decode in parallel on 3.14t.

