Jam
Jam is a typed, modular authentication and authorization framework for Python. It brings tokens, delegated credentials, sessions, identity protocols, and policy enforcement behind one consistent API without forcing applications into a particular web framework.
Use the high-level Jam or AsyncJam facade for complete authentication
flows, or use the JOSE, PASETO, Macaroon, SAML, OTP, session, and OAuth2
modules independently.
What Jam provides
| Area | Capabilities |
|---|---|
| Credentials | JWT, JWS, JWE, PASETO v1-v4, Macaroons, and SAML 2.0 |
| Stateful authentication | Sessions and shared credential token lists |
| Identity and verification | OAuth2 clients, HOTP, and TOTP |
| Authorization | Typed principals, credential constraints, contextual policy rules, and deny-by-default evaluation |
| Key management | In-memory and file-backed keychains, rotation, historical-key verification, and revocation |
| Integrations | Django, Django REST Framework, Django Modern REST, FastAPI, Starlette, Litestar, and Flask |
| Configuration | Python dictionaries, TOML, YAML, and JSON with environment-variable substitution |
| Runtime model | Synchronous and asynchronous facades with native async I/O backends |
Jam keeps optional features optional. The core package depends only on
cryptography; integrations and storage clients are installed through
extras when needed.
Installation
Jam requires Python 3.10 or newer.
pip install jamlib
Install only the optional dependencies your application uses:
pip install "jamlib[redis]" # Redis sessions and token lists
pip install "jamlib[oauth2]" # OAuth2 HTTP clients
pip install "jamlib[yaml]" # YAML configuration
pip install "jamlib[fastapi]" # FastAPI integration
pip install "jamlib[django]" # Django integration
pip install "jamlib[drf]" # Django REST Framework integration
Other extras include cli, json, toml, starlette, litestar, flask,
and dmr.
Quickstart
Create a minimal config.toml:
[jam.jose.jwt]
alg = "HS256"
secret_key = "$JWT_SECRET_KEY"
[jam.authz.rules]
"profile:read" = ["*"]
"post:create" = ["is_authenticated"]
Set a sufficiently strong secret outside the configuration file:
export JWT_SECRET_KEY="replace-with-at-least-32-random-characters"
Define the application subject and use the issue-authenticate-authorize flow:
from dataclasses import dataclass
from jam import BaseSubject, Jam
@dataclass
class User(BaseSubject):
id: str
email: str
is_authenticated: bool = True
jam = Jam(config="config.toml", subject=User)
user = User(id="1", email="user@example.com")
token = jam.issue(
user,
via="jwt",
exp=3600,
permissions=["profile:read", "post:create"],
)
principal = jam.authenticate(token, via="jwt")
assert principal.subject == user
assert jam.authorize(principal, "post:create")
assert not jam.authorize(principal, "post:delete")
Authentication returns a Principal: the verified subject, registered and
application claims, permissions, credential type, and any constraints carried
by the credential. Authorization evaluates credential constraints before
server-side policy, so a policy cannot widen authority granted by a token or
Macaroon.
One facade, multiple credential types
The facade selects a configured mechanism through via:
jwt = jam.issue(user, via="jwt", permissions=["profile:read"])
paseto = jam.issue(user, via="paseto", permissions=["profile:read"])
macaroon = jam.issue(user, via="macaroon", permissions=["profile:read"])
saml_response = jam.issue(user, via="saml")
principal = jam.authenticate(jwt, via="jwt")
Modules are also available directly when lower-level protocol control is needed:
decoded = jam.macaroon.decode(macaroon)
attenuated = decoded.add_caveat(b"permission = profile:read")
header_and_payload = jam.jwt.decode(jwt)
Only configured modules are available. Accessing a missing module fails with a
JamConfigurationError instead of an untyped None attribute error.
Asynchronous applications
AsyncJam provides native async session, token-list, and OAuth2 operations.
Cryptography, OTP, Macaroon processing, and authorization remain synchronous
because they do not cross an I/O boundary.
from jam.aio import AsyncJam
jam = AsyncJam(config="config.toml", subject=User)
token = await jam.issue(user, via="jwt", exp=3600)
principal = await jam.authenticate(token, via="jwt")
allowed = jam.authorize(principal, "profile:read")
await jam.aclose()
AsyncJam can also be used as an async context manager when it owns I/O
clients:
async with AsyncJam(config="config.toml", subject=User) as jam:
token = await jam.issue(user, via="session")
Authorization that credentials cannot bypass
Jam separates identity, credential authority, and application policy:
authenticate()verifies the credential and creates aPrincipal.- Credential constraints are evaluated first.
- The configured server-side policy evaluates the requested permission and context.
- Any failed constraint or policy rule denies access.
from jam.authz import AuthorizationContext
context = AuthorizationContext(
request=request,
resource=article,
)
if not jam.authorize(principal, "article:update", context):
raise PermissionError
Macaroon caveats, token permissions, time restrictions, and resource-aware policy checks can only reduce authority; they cannot silently grant permissions that the credential did not carry.
Framework integrations
Jam integrations are deliberately thin: they extract credentials, delegate to
Jam or AsyncJam, and expose the resulting principal to the framework.
Standalone modules
The facade is optional. Protocol implementations can be constructed directly:
from jam.jose import JWT
from jam.paseto import PASETOv4
jwt = JWT(alg="HS256", secret_key="replace-with-a-strong-secret")
paseto = PASETOv4(purpose="local", secret_key=b"\x00" * 32)
See the documentation for:
Development
git clone https://github.com/mkrdnk/jam.git
cd jam
uv sync --group tests --all-extras
uv run pytest -x
Jam is licensed under the Apache License 2.0.
Metadata
Release files for jamlib 4.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jamlib-4.2.1.tar.gz | 154.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jamlib-4.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 367.3 kB
Release files / jamlib-4.2.1.tar.gz
| Download URL | jamlib-4.2.1.tar.gz |
|---|---|
| Size | 154.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ebe50650f109840d8609682c7b0b986b96ba5e33a98da729aadd310185f66649
|
|
BLAKE2b-256 checksum How to use checksums |
bc925484c200614ef4e104d12cf9a0a088649c472e227921890c3736607125bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / jamlib-4.2.1-py3-none-any.whl
| Download URL | jamlib-4.2.1-py3-none-any.whl |
|---|---|
| Size | 213.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5f909709a444064b7c402008fb2a3b4ed401fb82c1c6069995bb9df61ba399a4
|
|
BLAKE2b-256 checksum How to use checksums |
c22b9d76a13112825b0e99c4f4746aaef476812d3071334e3310eef457c85038
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|