Library / SDK
jpadilla/pyjwt avatar
jpadilla/pyjwt

PyJWT: the small JWT library where the security work shows up

JSON Web Token implementation in Python

5,703 stars797 forksPythonMIT

At a glance

What is it?
A pure Python implementation of RFC 7519 whose recent releases are almost entirely a story about algorithm confusion attacks and key handling.
Who is it for?
PyJWT is the reference most Python services actually install, and reading its changelog is a better lesson in JWT than most tutorials. The API is four calls, the install is one line, and the hard part is not signing tokens but deciding which algorithms you will accept and refusing everything else.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 14 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Four calls and one optional dependency

The entire public surface a normal application touches is four lines long, and they are the four lines in the README's usage block:

python
>>> import jwt
>>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS256")
>>> print(encoded)
>>> jwt.decode(encoded, "secret", algorithms=["HS256"])
{'some': 'payload'}

`jwt.encode` takes a payload, a key and an explicit algorithm. `jwt.decode` takes the token, the key and an explicit list of allowed algorithms. Both the algorithm and the allow-list are keyword arguments you have to supply, which is the library quietly insisting on the two decisions that JWT implementations most often get wrong.

The install is `pip install PyJWT`, and the manifest carries one unconditional dependency: typing_extensions at 4.0 or newer, but only for Python below 3.11. The package requires Python 3.9 or newer and declares support through 3.15. The interesting part is the extras group, which is a single entry called crypto requiring cryptography 3.4.0 or newer. That is the boundary: HMAC signing and verification are pure Python and always work, while RSA and ECDSA verification need the optional package. A deployment that accepts RS256 tokens without installing the extra fails at verify time rather than at install time, which is a distinction worth knowing before you debug it.

The repository credits the original implementation to progrium and the current maintainer to Jose Padilla. It is classified as Production/Stable on PyPI, has 5,703 stars and 797 forks, and the last push was 2026-09-22.

Version 2.13.0 is a security release and the details matter

The 2.13.0 release, published 2026-05-21, bundles five security fixes plus three hardening changes, and the release notes recommend upgrading everyone. Two of them are worth reading closely because they describe real attack paths.

The first is advisory GHSA-xgmm-8j9v-c9wx, titled JWK JSON accepted as HMAC secret, with algorithm confusion named as the category. The write-up is precise: `HMACAlgorithm.prepare_key` previously rejected PEM-formatted and SSH-formatted asymmetric keys but did not catch a JWK passed as a raw JSON string. In a verifier configured with both symmetric and asymmetric algorithms in `algorithms=[...]` and given a raw-JSON JWK as the key, an attacker could forge HS256 tokens using the JWK text as the HMAC secret. The fix extends the guard to reject any JWK-shaped JSON.

The second is GHSA-jq35-7prp-9v3f, an algorithm allow-list bypass involving `PyJWK` and `PyJWKClient`. When verifying with a `PyJWK`, the caller's `algorithms=[...]` list was checked against the token header's `alg` value as a string only, while actual verification used the algorithm bound to the `PyJWK` object. An attacker who controlled a registered JWKS key could therefore sign with something the allow-list did not name.

Both bugs share a root cause that is worth internalising separately from the fixes: PyJWT has several ways to describe a key, and if the code path that resolves a key does not apply the same rules as the code path that checks the allow-list, the two can disagree. That is the shape of the bug class, not just these two instances.

Why the algorithm argument is mandatory rather than optional

Almost every JWT vulnerability you will read about traces back to a verifier that accepts whatever algorithm the token claims. The classic attack takes a server that expects RS256, has the RSA public key on hand, and is handed a token whose header says HS256. The server signs the token with HMAC using the public key as the secret, and the signature verifies. The attacker did not need the private key at all.

PyJWT closes this off at the API level. `jwt.encode` requires `algorithm=`. `jwt.decode` requires `algorithms=[...]`, a list, not a string and not a default. There is no code path in the documented usage where the library infers the algorithm from the token, which means an application that gets this wrong fails loudly rather than silently accepting a forged token.

That is a design choice rather than an accident, and it explains why the allow-list appears in the README's four-line example at all. It also explains the asymmetry in the two advisories above: one of them required a verifier deliberately configured with both symmetric and asymmetric algorithms in the list, which is a narrower and more unusual setup than the classic confusion attack. Even so, the position the library takes is that listing two families of algorithm in one verifier is a trap, and it now rejects key material that would let the trap spring.

The 2.14.0 release, published 2026-09-11, points readers at the changelog and the related security advisories rather than repeating them, which tells you the 2.13.0 notes are the reference document to keep on hand.

A tidy tree with the security policy kept in the open

The repository layout is small enough to read in a minute. The `jwt/` directory is the package itself, `tests/` holds the test suite, `docs/` builds to pyjwt.readthedocs.io, and there is a `CHANGELOG.rst` at the root next to `AUTHORS.rst`, `CODE_OF_CONDUCT.md`, `LICENSE` and `SECURITY.md`. A separate `tox.ini` drives the multi-environment test run the README points at with a single `tox` invocation, and `.pre-commit-config.yaml` handles formatting.

The build configuration moved to `pyproject.toml` with setuptools as the backend at 77.0.3 or newer, and the package uses PEP 621 metadata throughout: authors, classifiers, dependencies and keywords are all declared there rather than in a setup.py. The dev dependency group pins coverage at 7.10.7 and pytest in the 8.4 series, with cryptography at 3.4.0 or newer for the crypto tests, plus sphinx and the sphinx-rtd-theme for docs. Test-only groups named docs and tests let contributors install a slice rather than the whole thing.

The dependency groups are defined under a dependency-groups key rather than optional-dependencies, with the one exception of crypto, which is a real extra because end users need it. That distinction is the packaging story in miniature: contributors get a full environment, users get one optional extra, and the runtime dependency list stays at a single conditional entry.

Coverage is configured with source paths that include site-packages and the jwt directory, which is the standard trick for measuring a package after installation, and the report excludes `if TYPE_CHECKING` blocks so type-only lines do not distort the numbers.

Editorial conclusion

PyJWT is the reference most Python services actually install, and reading its changelog is a better lesson in JWT than most tutorials. The API is four calls, the install is one line, and the hard part is not signing tokens but deciding which algorithms you will accept and refusing everything else. That is exactly where version 2.13.0 spent its effort, closing an algorithm confusion path and an allow-list bypass that both worked by tricking the library about what a key was. The optional crypto extra is the other thing to understand, because RSA and ECDSA verification silently need the cryptography package and HMAC does not. Start with pip install PyJWT, pass an explicit algorithms list on every decode call, and read SECURITY.md before you rely on a JWKS endpoint you do not control.

Frequently asked questions

How do I install PyJWT?

Install it with pip: `pip install PyJWT`. That is the whole requirement for HMAC tokens such as HS256. If you plan to verify RSA or ECDSA tokens you also need the crypto extra, which pulls in the cryptography package at 3.4.0 or newer.

Is it possible to decode a PyJWT without the secret key?

You can read the payload without the key, because JWT payloads are base64url encoded rather than encrypted. What you cannot do is verify the signature without the right key, and that verification is the only thing that makes a token trustworthy. Any code that trusts claims from an unverified decode is trusting whoever signed the token.

What are JWTs used for?

They carry signed claims between parties without a shared session store, which is why they show up in authentication and in service-to-service calls. The signature proves the claims were issued by the holder of a key you already trust, and the claim set usually carries an issuer, an audience and an expiry that the verifying side checks.

What is the difference between JWT and PyJWT?

JWT is the specification, RFC 7519, which defines the token format and the algorithms. PyJWT is one Python library that implements it, and it is not the only one. The practical difference between libraries is less about format support and much more about how strictly they handle the algorithm argument and key material, which is where PyJWT's recent releases have focused.

Official sources

  1. jpadilla/pyjwt on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jpadilla-pyjwt.svg)](https://hysenlabs.com/projects/jpadilla-pyjwt)