Skip to content
ecdsa.com
Python

Authlib (authlib.jose)

Authlib's JOSE module, the token layer under Authlib's OAuth and OpenID Connect clients and servers: deprecated in 1.9 in favour of joserfc, it decodes without validating claims and allows alg: none once you list it.
License
BSD-3-Clause
Checked version
1.9.1 ·
Source
github.com/authlib/authlib
Documentation
docs.authlib.org/en/latest/

Compatibility at a glance

JOSE and JWT

JWS algorithms
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

Authlib is a full OAuth 2.0 and OpenID Connect toolkit for Flask, Django, Starlette and httpx, and authlib.jose is the JWS, JWE, JWK and JWT layer it was built on. Importing it in 1.9.1 emits authlib.jose module is deprecated, please use joserfc instead.; the maintainers moved the JOSE code into the separate joserfc package, and the old module is kept for compatibility until 2.0. Existing code keeps working, but new code should target joserfc.

The detail that bites most often is that decode() checks only the signature. A token that had expired an hour earlier came back from decode() without complaint; expiry, issuer and audience are checked only when you call claims.validate() — with claims_options marking exp and iss as essential, because a token without them otherwise passes too. Leeway is zero unless you pass one to validate().

Algorithm handling depends on which object you use. The module-level jwt accepts every algorithm it knows and picks by key type, so an RS256 token presented with an EC key failed at key import; a JsonWebToken(["ES256"]) instance allows only what you list. Listing none is enough to make an unsigned token verify, so never let a configuration value end up in that list. HMAC keys that look like PEM public keys are refused with This key may not be safe to import, and ES256 refuses a P-384 key.

Supported algorithms in 1.9.1 cover the HS, RS, PS and ES families, ES256K and EdDSA, but not the Ed25519 or Ed448 names. ES256 signatures are 64 bytes of raw r‖s; a DER signature fails with bad_signature, which the JWT debugger and the DER ⇄ raw converter help diagnose. To check what an identity provider's tokens and keys look like, use the OIDC token validator and the JWKS checker.

The sample uses a restricted JsonWebToken instance and validates the claims explicitly — the two steps that the module-level API leaves out.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/authlib/run.sh ran Authlib (authlib.jose) 1.9.1 on cryptography 50.0.2 in the container image python:3.14.8-slim-trixie on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • Status of the JOSE module

    deprecated

    Importing authlib.jose in 1.9.1 emits authlib.jose module is deprecated, please use joserfc instead.

    Recorded by the check
    fact jose-module-status = deprecated
    evidence jose-module-status: authlib.jose module is deprecated, please use joserfc instead.
  • JWS algorithms

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

    HS, RS, PS and ES at 256/384/512 bits, ES256K and EdDSA signed and verified; Ed25519 and Ed448 raised UnsupportedAlgorithmError.

    Recorded by the check
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, ES256K, EdDSA
    evidence jws-algorithms: Ed25519: UnsupportedAlgorithmError: unsupported_algorithm: 
    evidence jws-algorithms: Ed448: UnsupportedAlgorithmError: unsupported_algorithm: 
    evidence jws-algorithms: algorithms the module-level authlib.jose.jwt accepts: ES256, ES256K, ES384, ES512, EdDSA, HS256, HS384, HS512, PS256, PS384, PS512, RS256, RS384, RS512
  • ES256 signature encoding

    raw

    ES256 signatures are 64 bytes of raw r‖s.

    Recorded by the check
    fact jws-ecdsa-encoding = raw
  • DER signature inside a JWS

    rejected

    A token with a DER-encoded ES256 signature fails with BadSignatureError: bad_signature.

    Recorded by the check
    fact jws-der-signature = rejected
    evidence jws-der-signature: BadSignatureError: bad_signature: 
  • Algorithm allow-list

    from-key

    The module-level jwt accepts any algorithm it supports for the key type: an ES256 token verified with an EC key, while an RS256 token against the EC key failed with Invalid data for importing key.

    Recorded by the check
    fact jwt-algorithm-allowlist = from-key
    evidence jwt-algorithm-allowlist: authlib.jose.jwt.decode on ES256: verified; RS256 token with an EC key: ValueError: Invalid data for importing key
  • Unsigned tokens (alg none)

    opt-in

    The default jwt raises UnsupportedAlgorithmError for alg: none, but JsonWebToken(["none"]) verifies an unsigned token.

    Recorded by the check
    fact jwt-alg-none = opt-in
    evidence jwt-alg-none: default jwt with an EC key: UnsupportedAlgorithmError: unsupported_algorithm: 
    evidence jwt-alg-none: JsonWebToken(['none']): verified
  • Public key used as an HMAC secret

    rejected

    A PEM public key used as an HS256 key raises ValueError: This key may not be safe to import, with the default object and with a restricted instance.

    Recorded by the check
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: JsonWebToken(['HS256']) with PEM text as key: ValueError: This key may not be safe to import
    evidence jwt-hmac-public-key: default jwt with PEM text as key: ValueError: This key may not be safe to import
  • Curve checked against alg

    enforced

    Signing ES256 with a P-384 key raises Key for "ES256" not supported, only "P-256" allowed.

    Recorded by the check
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: ValueError: Key for "ES256" not supported, only "P-256" allowed
  • Claims validated while decoding

    no

    decode() returned the claims of a token that had expired an hour earlier; only claims.validate() checks exp, nbf, iss and aud.

    Recorded by the check
    fact jwt-claims-validated-on-decode = no
    evidence jwt-claims-validated-on-decode: decode() of a token expired an hour ago without validate(): returned claims
  • Default clock skew

    0s

    validate() uses no leeway by default: a token that expired three seconds earlier was rejected.

    Recorded by the check
    fact jwt-clock-skew = 0s
  • Expiry claim required

    no

    A token without exp passed validate(); mark it essential in claims_options to require it.

    Recorded by the check
    fact jwt-exp-required = no

Usage

Issue and verify an ES256 token with an instance restricted to ES256, then validate the claims explicitly.
import time

from authlib.jose import JsonWebKey, JsonWebToken

key = JsonWebKey.generate_key("EC", "P-256", is_private=True)
public_jwk = key.as_dict(is_private=False)

# A JsonWebToken instance restricted to ES256 instead of the permissive module-level jwt.
es256 = JsonWebToken(["ES256"])
token = es256.encode({"alg": "ES256"}, {"sub": "user-42", "iss": "https://auth.example.com", "exp": int(time.time()) + 600}, key)

claims = es256.decode(
    token,
    public_jwk,
    claims_options={"iss": {"essential": True, "value": "https://auth.example.com"}, "exp": {"essential": True}},
)
claims.validate(leeway=30)  # decode() checks only the signature; validate() checks the claims
print("sub:", claims["sub"])

The snippet harness runs every sample verbatim in the container image python:3.14.8-slim-trixie, and the run fails unless the output matches.

Setup
pip install --quiet --root-user-action=ignore Authlib==1.9.1 cryptography==50.0.2
Command
python usage.py
Output must match (regular expression)
sub: user-42

Libraries