ProvenPin Install

joserfc JWT signing + JWE encryption round-trip: working versions (tested 2026-10-18)

Sign and verify JWTs and encrypt JWE tokens with joserfc using OctKey and RSAKey, including claims validation and expiry, all offline.

passed6 passed · 0 failed2026-10-18 03:57 UTC
Status
passed
Last passed
2026-10-18 03:57 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions2
joserfc@1.7.5pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 39095370682

The code

Exactly the files that passed. Your agent can pull them with get_recipe("joserfc-jwt-jwe-roundtrip") (install), or see them on GitHub.

requirements.txt
joserfc==1.7.5
pytest==9.1.1
tokens.py
import datetime as dt

from joserfc import jwt
from joserfc.jwe import JWERegistry
from joserfc.jwk import OctKey, RSAKey
from joserfc.jwt import JWTClaimsRegistry

ISSUER = "https://issuer.example"
JWE_REGISTRY = JWERegistry()


def now() -> int:
    return int(dt.datetime.now(dt.UTC).timestamp())


def issue_hs256(claims: dict, secret: str) -> str:
    return jwt.encode({"alg": "HS256"}, claims, OctKey.import_key(secret))


def read_hs256(token: str, secret: str) -> dict:
    return jwt.decode(token, OctKey.import_key(secret)).claims


def validate(claims: dict, issuer: str = ISSUER) -> None:
    JWTClaimsRegistry(
        iss={"essential": True, "value": issuer},
        exp={"essential": True},
    ).validate(claims)


def rsa_pair(size: int = 2048) -> tuple[RSAKey, RSAKey]:
    private = RSAKey.generate_key(size)
    public = RSAKey.import_key(private.as_dict(private=False))
    return private, public


def sign_rs256(claims: dict, private: RSAKey) -> str:
    return jwt.encode({"alg": "RS256"}, claims, private)


def verify_rs256(token: str, public: RSAKey) -> dict:
    return jwt.decode(token, public).claims


def encrypt_rsa_oaep(claims: dict, public: RSAKey) -> str:
    header = {"alg": "RSA-OAEP", "enc": "A256GCM"}
    return jwt.encode(header, claims, public, registry=JWE_REGISTRY)


def decrypt_rsa_oaep(token: str, private: RSAKey) -> dict:
    return jwt.decode(token, private, registry=JWE_REGISTRY).claims
tests/test_tokens.py
import pytest
from joserfc.errors import BadSignatureError, ExpiredTokenError, UnsupportedKeyOperationError

from tokens import (
    ISSUER,
    decrypt_rsa_oaep,
    encrypt_rsa_oaep,
    issue_hs256,
    now,
    read_hs256,
    rsa_pair,
    sign_rs256,
    validate,
    verify_rs256,
)

SECRET = "a-32-byte-secret-used-only-in-tests!!"


@pytest.fixture(scope="module")
def rsa_keys():
    return rsa_pair()


def test_hs256_roundtrip_and_claims():
    token = issue_hs256({"iss": ISSUER, "sub": "u1", "exp": now() + 60}, SECRET)
    claims = read_hs256(token, SECRET)
    assert claims["sub"] == "u1" and claims["iss"] == ISSUER
    validate(claims)


def test_expired_token_is_rejected_by_registry():
    token = issue_hs256({"iss": ISSUER, "exp": now() - 1}, SECRET)
    claims = read_hs256(token, SECRET)
    with pytest.raises(ExpiredTokenError):
        validate(claims)


def test_wrong_secret_fails_signature():
    token = issue_hs256({"iss": ISSUER}, SECRET)
    with pytest.raises(BadSignatureError):
        read_hs256(token, SECRET + "x")


def test_rs256_verifies_with_public_key(rsa_keys):
    private, public = rsa_keys
    token = sign_rs256({"iss": ISSUER, "sub": "u1"}, private)
    assert verify_rs256(token, public)["sub"] == "u1"


def test_rsa_oaep_jwe_roundtrip(rsa_keys):
    private, public = rsa_keys
    token = encrypt_rsa_oaep({"iss": ISSUER, "exp": now() + 60}, public)
    assert token.count(".") == 4
    claims = decrypt_rsa_oaep(token, private)
    validate(claims)


def test_jwe_cannot_be_decrypted_with_public_key(rsa_keys):
    _, public = rsa_keys
    token = encrypt_rsa_oaep({"iss": ISSUER}, public)
    with pytest.raises(UnsupportedKeyOperationError):
        decrypt_rsa_oaep(token, public)
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.