On 14 October 2026, Authlib shipped two releases six hours apart. Authlib is the Python library that many applications use to issue and verify JSON Web Tokens (JWTs) and to run OAuth and OpenID Connect. Between them, the two releases fix CVE-2026-114615 and CVE-2026-114616. Both flaws lead to the same result: an attacker with no account creates a token that your application accepts as genuine, carrying whatever user, role or tenant they chose to write into it.
Authlib is downloaded more than 100 million times a month, and many teams that run it have never chosen it: it arrives as a dependency of OAuth login extras in web frameworks, admin panels, data tools and self-hosted AI interfaces. As of 29 October, there are no reports of either flaw being exploited.
Below: what the two flaws are, who is actually exposed, why the first fixed release is not the one you want, how to find every copy of Authlib you run, and what to change in code if you cannot upgrade today.
What a Forged Token Means
A JWT is a set of claims (who the user is, what they may do, when the token expires) plus a signature. The application trusts the claims because the signature checks out. If an attacker can produce a valid signature, the application has no other way to tell a forged token from a real one.
In practice that means acting as any user, including administrators, and in multi-tenant systems as any customer. Login and multi-factor authentication do not stand in the way, because the token is what a successful login produces. The attacker simply skips that step.
The Two Flaws
CVE-2026-114616: An Empty Secret Signs for Everyone
Tokens signed with HS256, HS384 or HS512 use a shared secret: the same value signs and verifies. Before version 1.9.1, Authlib accepted an empty secret. With an empty secret, the only unknown in the signature is gone, so anyone can compute a valid one for any claims they like.
No one sets an empty secret on purpose. It happens through configuration:
- A missing environment variable with an empty fallback, such as
os.environ.get("JWT_SECRET", ""). A new environment, a Kubernetes Secret reference marked optional, or a missing line in a deployment file is enough. - A database or configuration field that exists but was never filled in.
- A key lookup by key ID (
kid) that returns an empty value on a miss instead of raising an error. That one is the worst: the attacker controls thekidin the token, so they can trigger the miss themselves.
It is rated 7.4 (High), because the application has to be in one of these states. The catch is that nobody notices: an application running with an empty secret works perfectly, and its own tokens verify fine. Testing for it costs an attacker one request per endpoint.
Affected: every Authlib version before 1.9.1, including 1.9.0. Fixed in 1.9.1.
CVE-2026-114615: A Public Key That Works as a Password
Tokens signed with RS256 or ES256 work differently: the issuer signs with a private key, and everyone verifies with the matching public key, which is public by design.
The old attack against this setup is called algorithm confusion. The attacker labels a token HS256 and signs it using the public key as if it were a shared secret. A library that accepts both kinds of algorithm with the same key material will verify it. Authlib blocked this in 2024 by refusing to use anything that looks like a PEM key as an HMAC secret.
The check only looked at the first byte. A PEM file that starts with a blank line, a comment line or a byte-order mark (some editors, especially on Windows, add one silently) slipped past the check, while Authlib's PEM loader still read the key normally. We reproduced it in a container: on 1.7.2 and 1.8.0, a token signed with the public key was accepted for all three prefixes.
Three conditions have to line up: the application verifies with Authlib's default decoder, which accepts every algorithm and is the documented way to use it; the public key file starts with one of those prefixes; and the attacker has the exact bytes of that file, prefix included. Public keys get published, committed to repositories and sent to partners, so the third condition is weaker than it sounds. That combination is why it scores 9.1 on the newest CVSS scale and 7.4 on the older one.
Affected: versions 1.3.1 up to 1.9.0, not including it. Fixed in 1.9.0.
The 1.9.0 Trap
Version 1.9.0 fixed CVE-2026-114615 in the morning. Version 1.9.1, six hours later, fixed CVE-2026-114616. A team that upgraded quickly to 1.9.0 and closed the ticket is still exposed to the empty-secret flaw, and our test confirmed it: 1.9.0 still accepts a token signed with an empty secret, 1.9.1 refuses it with oct key material must not be empty.
The target version is 1.9.1, everywhere.
Who Is Exposed
- Python services that issue or verify their own JWTs with Authlib. APIs built on FastAPI, Flask or Django, internal OAuth and OpenID Connect servers, and gateways written in Python. These are the ones to look at first.
- Applications that use Authlib only to log users in through an external identity provider. The risk is usually lower here, because provider tokens are normally verified against published key sets rather than PEM files you load yourself. Upgrade anyway.
- Tools that bundle Authlib. Frameworks and admin panels pull it in through optional OAuth extras, and at least one widely used self-hosted AI chat interface pins an exact older Authlib version in its current release. In that case you do not control the version: upgrading Authlib underneath the tool conflicts with its pin, and the fix has to come from the tool's own update.
- Distribution packages. As of 29 October, neither Debian's nor Ubuntu's stable releases had shipped a fixed
python3-authlib. Most applications install Authlib from PyPI, but if yours imports the system package, the distribution's version is the one that counts.
How Big This Is
- More than 100 million downloads a month. Most of them are pulled in automatically as dependencies, which is exactly why the inventory in Step 1 matters.
- The same mistake, in several languages. Accepting an empty HMAC key is a known family of bugs: this year the same bug was reported in JWT libraries for JavaScript and Ruby, and in Authlib's own successor library. Attackers know what to try.
- Cheap to test for at scale. A token signed with an empty secret is the same for every target. Spraying it at login-protected APIs costs almost nothing.
The honest caveat: both flaws need a condition in your configuration, there are no known attacks as of 29 October, and neither flaw is on the official list of vulnerabilities known to be exploited. That can change in days, as it did for other flaws this month. The upgrade is small, so do it now.
Not sure where Authlib sits in your stack, or how your services handle signing keys? Request a free token security assessment and our security team will map it with you.
Step 1: Find Every Copy of Authlib
Start where it is installed, not where you think it is. pip show also tells you which package pulled it in:
pip show authlibName: Authlib
Version: 1.7.2
Required-by: some-toolIn repositories, check the requirement files and lock files:
grep -iE '^authlib' requirements*.txt
grep -i -A1 'name = "authlib"' poetry.lock uv.lockWhat runs in production is the container image, not the repository. Many images have no pip, but the installed package leaves a directory with the version in its name:
docker run --rm --entrypoint sh your-image:tag -c \
'find / -path /proc -prune -o -iname "authlib-*.dist-info" -print 2>/dev/null'On Kubernetes, ask the running pods directly:
kubectl exec deploy/your-app -- python -c "import authlib; print(authlib.__version__)"Anything below 1.9.1 needs action, whether it appears as a direct dependency or not.
Step 2: Upgrade to 1.9.1
Raise the floor in whatever manages your dependencies:
pip install --upgrade "authlib>=1.9.1"
poetry add "authlib>=1.9.1"
uv add "authlib>=1.9.1"With plain requirement files, write authlib>=1.9.1 (or pin authlib==1.9.1) and regenerate any compiled lock. Then rebuild the images and redeploy: the fix only reaches production inside a new image.
Afterwards, let pip tell you whether anything installed disagrees with the new version:
pip checksome-tool 1.0 has requirement authlib==1.7.2, but you have authlib 1.9.1.A line like that means a tool pins the old version. Do not force it: update the tool, or ask its maintainers for a release, and until then keep it off networks an attacker can reach.
You will also see a deprecation warning for Authlib's jose module, pointing to its successor library. That migration is separate work, so do not let it delay this upgrade.
Step 3: If You Cannot Upgrade Today
Two code changes each close one of the flaws, on any Authlib version. Both are worth keeping after you upgrade.
Accept only the algorithm you actually issue. This closes CVE-2026-114615:
from authlib.jose import JsonWebToken
# Only RS256 is accepted. An HS256 token signed with your
# public key is rejected before any key is used.
jwt = JsonWebToken(["RS256"])
claims = jwt.decode(token, public_key)
claims.validate()We tested this on 1.7.2 and 1.8.0 with all three key prefixes: genuine RS256 tokens still verify, and the forged HS256 tokens are rejected. Do not try to fix the key file with .strip() instead: it removes whitespace, but a comment line or a byte-order mark survives it.
Refuse to start without a real secret. This closes CVE-2026-114616:
import os
def load_hmac_secret(name="JWT_SECRET", min_len=32):
value = os.environ.get(name)
if not value or len(value) < min_len:
raise RuntimeError(f"{name} is missing or shorter than {min_len} characters")
return value.encode()Call it at startup, so a missing secret stops the deployment instead of running quietly with an empty one. Apply the same rule to key lookups by kid: raise on a miss, never return an empty value.
Step 4: Check Whether Anyone Got There First
If Step 1 or Step 3 turned up an empty secret, or a public key file with a prefix verified through the default decoder, treat every token issued since that configuration went live as suspect:
- Rotate. Set a real secret or generate a new key pair. Every existing token stops working and users sign in again, which is the point.
- Look for algorithms you never issue. The first part of a JWT is its header, a small piece of base64url-encoded JSON. If your services only issue RS256, a token whose header says HS256 is a forgery attempt. Log the algorithm and key ID of every token at the point where it is verified, accepted or rejected.
- Look for claims your system never creates. Users that do not exist, roles you do not define, tenants that do not match the account, or administrator actions at hours nobody works.
The Real Problem
Neither flaw needs a careless developer. One needs a missing environment variable, the other a blank line at the top of a key file. Both are configuration slips that a secure library would refuse, and both turn into authentication bypasses because the library did not.
The harder question is inventory. A library that arrives as a dependency of a dependency, inside an image built months ago by a team that has moved on, does not get upgraded because nobody knows it is there. The teams that close this in a day are the ones that can answer "where do we run Authlib, and which version?" from a list, not from a search.
Free Token Security Assessment
The free assessment covers:
- An inventory of every Authlib copy you run across repositories, lock files, container images and running pods, including the ones that came in as dependencies of other tools
- A review of how your services sign and verify tokens: which algorithms they accept, how keys are loaded, and what happens when a secret is missing
- A prioritised plan: what to upgrade first, which tools are blocking the upgrade, and where keys need rotating
You get a written list of what is exposed and what to do about it, with no obligation attached.
Request your free token security assessment, or read more about our DevSecOps practice, AI security for self-hosted AI tools that bundle libraries like this one, and our cybersecurity practice. The same "patched is not the same as fixed" trap hit GitLab this week.