Functional Weave
Code in Python

auth.jwt@1.0.0

impl/python/sign_jwt.py

2,396 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import json
from typing import Any, Mapping, Sequence

from .auth_jwt_decode_jwt import check_jwt_secret  ← decodeJwt, another function of this group · built into the same file, even by a slim install
from .crypto_hmac_sha256 import hmac_sha256  ← from crypto.hmac-sha256 ^1.0.0 · built alongside by fune
from .encoding_base64_base64_url_encode import base64_url_encode
from .encoding_utf8_utf8_encode import utf8_encode

#: base64url of {"alg":"HS256","typ":"JWT"}: the header is fixed.
HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
MAX_DEPTH = 32
MAX_SAFE = 9007199254740991


def _canonical(value: Any, depth: int) -> Any:
    """The value with every number made a whole int, checked the way the other
    languages check it; json.dumps then writes it with sorted keys."""
    if value is None or isinstance(value, (bool, str)):
        return value
    if isinstance(value, (int, float)):
        if isinstance(value, float) and not value.is_integer():
            raise ValueError("claims may only hold whole numbers from -(2^53 - 1) to 2^53 - 1")
        if abs(value) > MAX_SAFE:
            raise ValueError("claims may only hold whole numbers from -(2^53 - 1) to 2^53 - 1")
        return int(value)
    if isinstance(value, (list, tuple)):
        if depth > MAX_DEPTH:
            raise ValueError("claims are nested more than 32 deep")
        return [_canonical(item, depth + 1) for item in value]
    if isinstance(value, Mapping):
        if depth > MAX_DEPTH:
            raise ValueError("claims are nested more than 32 deep")
        out = {}
        for key, item in value.items():
            if not isinstance(key, str):
                raise TypeError("claims must hold only JSON values")
            out[key] = _canonical(item, depth + 1)
        return out
    raise TypeError("claims must hold only JSON values")


def sign_jwt(claims: Mapping[str, Any], secret: Sequence[int]) -> str:
    """An HS256 JWT for the claims: header {"alg":"HS256","typ":"JWT"}, the
    claims as canonical JSON (no spaces, keys sorted by code point, UTF-8
    rather than \\u escapes), and the HMAC-SHA256 of the two."""
    check_jwt_secret(secret)
    if not isinstance(claims, Mapping):
        raise TypeError("claims must be a JSON object")
    payload = json.dumps(_canonical(claims, 1), sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False)
    signing_input = HEADER + "." + base64_url_encode(utf8_encode(payload))
    return signing_input + "." + base64_url_encode(hmac_sha256(secret, utf8_encode(signing_input)))