Skip to content

yggdrasil.jwt

jwt

yggdrasil JWT integration.

Parsing-only primitives for JSON Web Tokens (RFC 7519). Signature verification is intentionally out of scope — that needs an algorithm plug-in (PyJWT, cryptography) and a key-resolution policy, and both belong in the caller, not in a base-install primitive.

Quick start

>>> from yggdrasil.jwt import JWTToken
>>>
>>> tok = JWTToken.from_("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMifQ.sig")
>>> tok.alg, tok.sub
('HS256', '123')
>>>
>>> # Strip the Bearer prefix from an Authorization header in one call:
>>> JWTToken.from_authorization("Bearer " + tok.raw).sub
'123'

JWTParseError

Bases: ValueError

Raised when a value can't be parsed as a JWT.

Subclasses :class:ValueError so callers that already catch the generic shape (except ValueError:) keep working.

JWTToken dataclass

JWTToken(
    raw: str,
    header_segment: str,
    payload_segment: str,
    signature_segment: str,
    header: Mapping[str, Any],
    payload: Mapping[str, Any],
    signature: bytes,
)

A parsed JSON Web Token (RFC 7519).

Holds both the decoded view (header / payload / signature) and the raw view (header_segment / payload_segment / signature_segment, plus raw). The raw segments are kept so a caller that wants to verify the signature later has the exact bytes that were signed (header.payload) without re-encoding — base64url round-trips aren't byte-stable when the original token used no padding (the wire shape).

The class doesn't verify the signature — that needs an algorithm plug-in and a key resolver, which the caller is in a better position to wire up. Parsing alone is the common use case (peek at exp / sub / aud before forwarding the token).

alg property

alg: str | None

alg header claim — the signing algorithm (e.g. HS256).

typ property

typ: str | None

typ header claim — usually JWT.

kid property

kid: str | None

kid header claim — key identifier used for verification.

iss property

iss: str | None

iss claim — issuer.

sub property

sub: str | None

sub claim — subject.

aud property

aud: str | tuple[str, ...] | None

aud claim — audience.

RFC 7519 allows either a single string or an array of strings. Lists are normalized to a tuple so the value stays hashable and the token instance stays effectively immutable.

jti property

jti: str | None

jti claim — JWT ID (unique per token).

exp property

exp: int | float | None

exp claim — raw NumericDate (seconds since epoch).

iat property

iat: int | float | None

iat claim — raw NumericDate (issued-at seconds).

nbf property

nbf: int | float | None

nbf claim — raw NumericDate (not-before seconds).

expires_at property

expires_at: datetime | None

exp as a UTC :class:~datetime.datetime, or None.

issued_at property

issued_at: datetime | None

iat as a UTC :class:~datetime.datetime, or None.

not_before_at property

not_before_at: datetime | None

nbf as a UTC :class:~datetime.datetime, or None.

signing_input property

signing_input: bytes

The exact bytes a verifier feeds into the signing algorithm.

Defined by RFC 7515 §5.2 as ASCII(BASE64URL(header)) || '.' || ASCII(BASE64URL(payload)) — the two raw segments with the dot between them, no re-encoding. Surfaced so a future signature-verification helper doesn't have to reconstruct it from the decoded views.

from_ classmethod

from_(value: Any) -> 'JWTToken'

Generic dispatch — parse a JWT from whatever the caller has.

Accepts:

  • an existing :class:JWTToken (returned as-is, identity short-circuit),
  • a :class:str — delegated to :meth:from_str,
  • :class:bytes / :class:bytearray / :class:memoryview — delegated to :meth:from_bytes.

Other shapes raise :class:JWTParseError with the value's type so the caller sees what they passed.

from_str classmethod

from_str(value: str) -> 'JWTToken'

Parse a JWT from a string.

Accepts the bare a.b.c form and the Bearer a.b.c form from an Authorization header — the prefix is stripped transparently. Surrounding whitespace is tolerated.

from_bytes classmethod

from_bytes(value: bytes) -> 'JWTToken'

Parse a JWT from raw bytes — must be ASCII (base64url + dots).

from_authorization classmethod

from_authorization(header_value: str | None) -> 'JWTToken | None'

Parse the JWT out of an Authorization header value.

Returns None when the value is empty, missing, or doesn't carry a Bearer-style token — callers usually want a no-token path instead of a try/except. Use :meth:from_str directly if a missing token should raise.

is_expired

is_expired(*, now: datetime | None = None, leeway: float = 0.0) -> bool

Return whether the token's exp has passed.

Tokens without an exp claim are treated as non-expiring (returns False) — the general "absence of a claim is permissive" reading of RFC 7519. If your verifier requires exp, gate on token.exp is None before calling this.

leeway (seconds) widens the window to absorb clock skew between issuer and verifier.

is_not_yet_valid

is_not_yet_valid(*, now: datetime | None = None, leeway: float = 0.0) -> bool

Return whether nbf is still in the future.

Mirrors :meth:is_expired's shape — leeway widens the window in the caller's favor. Tokens without nbf are always valid by this check.