Skip to main content

Elegant JWT

EO principles respected here

JSON Web Tokens in the Elegant Objects style. The library hides pyjwt behind small immutable objects: a Token, its Claims, and a Signature that owns the algorithm and the key.

Installation

uv add elegant-jwt

or

pip install elegant-jwt

Quick Start

Create a token and read it back:

from elegant_jwt import Hs256, JwtClaims, JwtToken

signature = Hs256("a-secret-of-at-least-thirty-two-bytes!")

raw = JwtClaims({"sub": "42"}).token(signature).value()
print(raw)  # => "eyJhbGciOiJIUzI1NiIs..."

claims = JwtToken(raw, signature).claims()
print(claims.json())  # => {"sub": "42"}

The algorithm is an object, never a hardcoded string. Pick Hs256, Rs256, or Es256, or implement the Signature interface yourself.

Tokens That Expire

Wrap your claims in ExpiringClaims to add an exp claim. The lifetime is in seconds:

from elegant_jwt import ExpiringClaims, Hs256, JwtClaims

signature = Hs256("a-secret-of-at-least-thirty-two-bytes!")

token = ExpiringClaims(
    JwtClaims({"sub": "42"}),
    3600,
).token(signature)

print(token.expired())   # => False
print(token.validity())  # => 3600 (seconds left until expiration)

Refusing Expired Tokens

StrictToken is a decorator that refuses to give claims from an expired token. Use it wherever an expired token must be treated as an error:

from elegant_jwt import ExpiringClaims, Hs256, JwtClaims, JwtToken, StrictToken

signature = Hs256("a-secret-of-at-least-thirty-two-bytes!")
raw = ExpiringClaims(JwtClaims({"sub": "42"}), 3600).token(signature).value()

token = StrictToken(JwtToken(raw, signature))
token.claims()  # raises Exception once the token has expired

A strict token needs an exp claim to judge freshness, so create it with ExpiringClaims.

Stamping the Issuer

IssuedClaims adds iat (issued at) and iss (issuer) claims. Decorators stack, each one adding its own claims on top:

from elegant_jwt import ExpiringClaims, Hs256, IssuedClaims, JwtClaims

token = IssuedClaims(
    ExpiringClaims(
        JwtClaims({"sub": "42"}),
        3600,
    ),
    "my-service",
).token(Hs256("a-secret-of-at-least-thirty-two-bytes!"))

print(token.claims().json())
# => {"sub": "42", "exp": 1788094023, "iat": 1788090423, "iss": "my-service"}

Asymmetric Algorithms

Rs256 and Es256 sign with a private key and verify with a public key, both in PEM format:

from elegant_jwt import JwtClaims, JwtToken, Rs256

signature = Rs256(private_pem, public_pem)

raw = JwtClaims({"sub": "42"}).token(signature).value()
claims = JwtToken(raw, signature).claims()

A service that only verifies tokens holds just the public key and never calls encoded.

Testing Without Waiting

Time is an input, not a hidden call. Every object that needs the current time accepts a Clock, so tests never sleep and never patch:

from elegant_jwt import Clock, ExpiringClaims, JwtClaims


class FrozenClock(Clock):
    def __init__(self, instant: int):
        self.instant = instant

    def moment(self) -> int:
        return self.instant


claims = ExpiringClaims(JwtClaims({"sub": "42"}), 60, FrozenClock(1000))
print(claims.json())  # => {"sub": "42", "exp": 1060}

Your Own Signature

Need a key from a JWKS endpoint, a vault, or a database? Implement the Signature interface and keep the policy (cache, retry, timeout) on your side; the library stays free of I/O:

from elegant_jwt import Signature


class VaultSignature(Signature):
    def __init__(self, vault: Vault):
        self.vault = vault

    def encoded(self, payload: dict) -> str:
        return Hs256(self.vault.secret()).encoded(payload)

    def decoded(self, raw: str, options: dict) -> dict:
        return Hs256(self.vault.secret()).decoded(raw, options)

Errors

Every failure raises a plain Exception with a human message, chained to the original cause:

try:
    JwtToken("not-a-token", signature).claims()
except Exception as trouble:
    print(trouble)  # => "The access token is not valid."

Design

  • Every class is immutable; a change produces a new object.
  • New behavior comes from decorators (StrictToken, ExpiringClaims, IssuedClaims), not from modification of existing classes.
  • The library performs no network and no filesystem access.

Development

make unit     # tests with coverage
make black    # formatting
make flake8   # style
make ruff     # lint

Release files for elegant-jwt 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for elegant-jwt 0.0.1
File Size Uploaded
elegant_jwt-0.0.1.tar.gz 72.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for elegant-jwt 0.0.1
File Interpreter ABI Platform
elegant_jwt-0.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 78.7 kB

Release files / elegant_jwt-0.0.1.tar.gz

Download URL elegant_jwt-0.0.1.tar.gz
Size 72.6 kB
Tags Source
SHA-256 checksum
How to use checksums
cb3b4e701451c305dd8954bc2de530b8e66b71a32afffd36a354b80f568b4432
BLAKE2b-256 checksum
How to use checksums
40b28e89261b9f8049ce362ba17d457517e5da914abf8707288d83a771d8e3dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","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 / elegant_jwt-0.0.1-py3-none-any.whl

Download URL elegant_jwt-0.0.1-py3-none-any.whl
Size 6.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8b72cb166b004fb9417ad2d744a922cf05cc352f7b8b4d926926fbd02629083b
BLAKE2b-256 checksum
How to use checksums
0dd81ef3c1530c7f1e241b807c1689a4dbd91079d7f2513816b60026cdb5bbeb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","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 history Release notifications | RSS feed

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

This release

0.0.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page