Code Security

Mar 5, 2026

Code-Level Breakdown of the pac4j JWT Authentication Bypass (CVE-2026-29000)

Amartya | CodeAnt AI Code Review Platform
Sonali Sood

Founding GTM, CodeAnt AI

Every authentication system ultimately comes down to one moment:

A piece of code decides whether to trust a token.

If that moment is implemented incorrectly, everything built on top of it collapses.

Recently, a vulnerability in the Java authentication library pac4j-jwt revealed exactly how fragile that moment can be. The issue, now tracked as CVE-2026-29000, allows attackers to authenticate as any user, including administrators, by forging encrypted tokens using only a public key.

  • No credentials.

  • No secrets.

  • No brute force.

Just a token crafted to trigger a specific execution path.

The full technical disclosure from the CodeAnt AI Security Research team is available here:

https://www.codeant.ai/security-research/pac4j-jwt-authentication-bypass-public-key

But instead of repeating the disclosure, this article focuses on something different:

What actually happens inside the authentication code when this exploit runs.

Because the most interesting part of this vulnerability is not the JWT itself.

It’s the code path that silently removes authentication from the authentication system.

The Trust Boundary

In most Java authentication stacks using pac4j, the trust boundary sits inside a component called:

This class processes incoming tokens and decides whether the request should be authenticated.

At a high level, the process looks like this:



If the token passes verification, the claims inside the JWT become the user identity.

That means one thing:

If verification fails, authentication must stop.

That invariant is what broke.

The Code Path

The vulnerability appears when pac4j processes encrypted JWT tokens (JWE).

The relevant execution flow looks roughly like this:



At first glance this seems perfectly reasonable.

But look carefully at the control flow.

The system only verifies the signature if a signed JWT exists.

If signedJWT is null, the verification block never runs.

And yet the authentication pipeline continues.

Eventually createJwtProfile() is called.

Which means the claims are accepted.

The authentication system just authenticated a token that was never verified.

Where the Null Comes From

The key line in this code path is:

This method is part of the Nimbus JOSE+JWT library.

Its behavior is simple:

  • If the payload is a signed JWT (JWS), return it.

  • If the payload is not signed, return null.

That includes tokens known as PlainJWT.

PlainJWT is part of the official JWT specification.

It represents a JWT with no signature at all.

So if an attacker can place a PlainJWT inside the encrypted wrapper, the method returns null.

Which leads to this execution path:



The authentication system just trusted a token without verifying its authenticity.

Crafting the Token

Now comes the interesting part.

To exploit this vulnerability, an attacker must construct a token that triggers the vulnerable path.

The process requires surprisingly little information.

The only requirement is access to the server’s RSA public key.

Public keys are usually easy to obtain.

Typical sources include:



This is normal.

Public keys are supposed to be public.

The vulnerability occurs because the authentication logic trusts encrypted tokens before verifying their signatures.

The Exploit Payload

The attacker first creates arbitrary claims.

Example:



These claims define the user identity.

Next, the attacker creates an unsigned token.

This token contains the attacker’s claims.

But it has no signature.

Under normal circumstances, such a token should never authenticate.

But the pac4j code path allows it.

Wrapping the Token

The attacker then wraps the unsigned token inside an encrypted JWE container.



This produces a valid encrypted token.

To the server, it looks exactly like a legitimate authentication token.

Because the encryption is valid.

And the server will successfully decrypt it.

Why Encryption Did Not Protect the System

At first glance, the attack might seem surprising.

The token is encrypted using JWE.

Encryption should protect the token from tampering.

But encryption does not guarantee authenticity.

JWT defines two separate security mechanisms:

JWE encryption (confidentiality)JWS signature (authenticity)
JWE encryption (confidentiality)JWS signature (authenticity)

Encryption ensures that the payload cannot be read without the key.

But it does not guarantee who created the token.

Authentication systems rely on signature verification to establish identity.

In the pac4j vulnerability, the system successfully decrypted the token.

But because the payload contained a PlainJWT, the signature verification step never executed.

Which means the authentication pipeline effectively became:

decrypt tokentrust payloadauthenticate user
decrypt tokentrust payloadauthenticate user

Instead of the intended flow:

decrypt tokenverify signaturetrust payloadauthenticate user
decrypt tokenverify signaturetrust payloadauthenticate user

This distinction is subtle but critical.

Encryption protects data secrecy.

Only signatures prove token authenticity.

The Moment the Exploit Triggers

When the token reaches the server, the following events occur:



From the perspective of the authentication system:

Nothing failed.

Every operation succeeded.

The token decrypted correctly.

The payload parsed correctly.

The claims were valid.

The system simply never verified who created the token.

Why the CVSS Score Is 10

The vulnerability received a CVSS score of 10.0, the maximum severity rating.

This rating reflects several factors.

The exploit is:

  • Network exploitable

  • No privileges required

  • No user interaction required

  • Authentication bypass

An attacker can impersonate any user including administrators simply by submitting a crafted token.

In many systems this effectively means complete system compromise.

Why the Bug Is Subtle

The most interesting aspect of this vulnerability is that none of the individual components are broken.

The JWT specification allows PlainJWT.

The Nimbus library behaves correctly.

The pac4j code performs a correct null check.

Every line of code appears reasonable.

But when these pieces interact, authentication disappears.

This is known as a composition vulnerability.

Security primitives work correctly.

But the assumptions between them are wrong.

How the Issue Was Discovered

The vulnerability was discovered during an internal research effort by the CodeAnt AI Security Research team focused on auditing patched vulnerabilities in open-source libraries.

Their analysis identified a suspicious control flow in the pac4j authentication logic where signature verification could be skipped under certain conditions.

A manual investigation confirmed that an attacker could exploit this path by embedding an unsigned token inside an encrypted wrapper.

The maintainers quickly confirmed the issue and released patched versions.

The Patch

The vulnerability has been fixed in:



The patch ensures that authentication cannot proceed unless the token’s signature is successfully verified.

Systems running earlier versions should upgrade immediately.

The Real Lesson

Authentication systems do not fail when cryptography fails.

They fail when assumptions fail.

In this case, the assumption was simple:

If a token decrypts successfully, it must contain a signed payload.

But the JWT specification allows something else.

PlainJWT.

That single edge case allowed an attacker to bypass the authentication system entirely.

The Danger of Silent Authentication Failures

The pac4j vulnerability demonstrates an uncomfortable reality of modern authentication systems:

Security does not fail when cryptography fails.

It fails when control flow fails.

In this case, encryption worked correctly.
The JWT parser behaved exactly as designed.
Every individual component followed its specification.

But a single conditional check allowed authentication to proceed without verifying the token’s signature.

That tiny gap removed the most important security guarantee in the system.

The lesson extends far beyond pac4j.

Any authentication system, whether built on JWT, OAuth, SAML, or custom tokens, must treat signature verification as non-negotiable.

If verification can be skipped, authentication is no longer authentication.

Final Thought

Every authentication system contains a moment where the code decides:

“Is this token trustworthy?”

In pac4j, that decision depended on a single null check.

And that null check removed authentication from the authentication process.

Every authentication system ultimately comes down to one moment:

A piece of code decides whether to trust a token.

If that moment is implemented incorrectly, everything built on top of it collapses.

Recently, a vulnerability in the Java authentication library pac4j-jwt revealed exactly how fragile that moment can be. The issue, now tracked as CVE-2026-29000, allows attackers to authenticate as any user, including administrators, by forging encrypted tokens using only a public key.

  • No credentials.

  • No secrets.

  • No brute force.

Just a token crafted to trigger a specific execution path.

The full technical disclosure from the CodeAnt AI Security Research team is available here:

https://www.codeant.ai/security-research/pac4j-jwt-authentication-bypass-public-key

But instead of repeating the disclosure, this article focuses on something different:

What actually happens inside the authentication code when this exploit runs.

Because the most interesting part of this vulnerability is not the JWT itself.

It’s the code path that silently removes authentication from the authentication system.

The Trust Boundary

In most Java authentication stacks using pac4j, the trust boundary sits inside a component called:

This class processes incoming tokens and decides whether the request should be authenticated.

At a high level, the process looks like this:


If the token passes verification, the claims inside the JWT become the user identity.

That means one thing:

If verification fails, authentication must stop.

That invariant is what broke.

The Code Path

The vulnerability appears when pac4j processes encrypted JWT tokens (JWE).

The relevant execution flow looks roughly like this:


At first glance this seems perfectly reasonable.

But look carefully at the control flow.

The system only verifies the signature if a signed JWT exists.

If signedJWT is null, the verification block never runs.

And yet the authentication pipeline continues.

Eventually createJwtProfile() is called.

Which means the claims are accepted.

The authentication system just authenticated a token that was never verified.

Where the Null Comes From

The key line in this code path is:

This method is part of the Nimbus JOSE+JWT library.

Its behavior is simple:

  • If the payload is a signed JWT (JWS), return it.

  • If the payload is not signed, return null.

That includes tokens known as PlainJWT.

PlainJWT is part of the official JWT specification.

It represents a JWT with no signature at all.

So if an attacker can place a PlainJWT inside the encrypted wrapper, the method returns null.

Which leads to this execution path:


The authentication system just trusted a token without verifying its authenticity.

Crafting the Token

Now comes the interesting part.

To exploit this vulnerability, an attacker must construct a token that triggers the vulnerable path.

The process requires surprisingly little information.

The only requirement is access to the server’s RSA public key.

Public keys are usually easy to obtain.

Typical sources include:


This is normal.

Public keys are supposed to be public.

The vulnerability occurs because the authentication logic trusts encrypted tokens before verifying their signatures.

The Exploit Payload

The attacker first creates arbitrary claims.

Example:


These claims define the user identity.

Next, the attacker creates an unsigned token.

This token contains the attacker’s claims.

But it has no signature.

Under normal circumstances, such a token should never authenticate.

But the pac4j code path allows it.

Wrapping the Token

The attacker then wraps the unsigned token inside an encrypted JWE container.


This produces a valid encrypted token.

To the server, it looks exactly like a legitimate authentication token.

Because the encryption is valid.

And the server will successfully decrypt it.

Why Encryption Did Not Protect the System

At first glance, the attack might seem surprising.

The token is encrypted using JWE.

Encryption should protect the token from tampering.

But encryption does not guarantee authenticity.

JWT defines two separate security mechanisms:

JWE encryption (confidentiality)JWS signature (authenticity)

Encryption ensures that the payload cannot be read without the key.

But it does not guarantee who created the token.

Authentication systems rely on signature verification to establish identity.

In the pac4j vulnerability, the system successfully decrypted the token.

But because the payload contained a PlainJWT, the signature verification step never executed.

Which means the authentication pipeline effectively became:

decrypt tokentrust payloadauthenticate user

Instead of the intended flow:

decrypt tokenverify signaturetrust payloadauthenticate user

This distinction is subtle but critical.

Encryption protects data secrecy.

Only signatures prove token authenticity.

The Moment the Exploit Triggers

When the token reaches the server, the following events occur:


From the perspective of the authentication system:

Nothing failed.

Every operation succeeded.

The token decrypted correctly.

The payload parsed correctly.

The claims were valid.

The system simply never verified who created the token.

Why the CVSS Score Is 10

The vulnerability received a CVSS score of 10.0, the maximum severity rating.

This rating reflects several factors.

The exploit is:

  • Network exploitable

  • No privileges required

  • No user interaction required

  • Authentication bypass

An attacker can impersonate any user including administrators simply by submitting a crafted token.

In many systems this effectively means complete system compromise.

Why the Bug Is Subtle

The most interesting aspect of this vulnerability is that none of the individual components are broken.

The JWT specification allows PlainJWT.

The Nimbus library behaves correctly.

The pac4j code performs a correct null check.

Every line of code appears reasonable.

But when these pieces interact, authentication disappears.

This is known as a composition vulnerability.

Security primitives work correctly.

But the assumptions between them are wrong.

How the Issue Was Discovered

The vulnerability was discovered during an internal research effort by the CodeAnt AI Security Research team focused on auditing patched vulnerabilities in open-source libraries.

Their analysis identified a suspicious control flow in the pac4j authentication logic where signature verification could be skipped under certain conditions.

A manual investigation confirmed that an attacker could exploit this path by embedding an unsigned token inside an encrypted wrapper.

The maintainers quickly confirmed the issue and released patched versions.

The Patch

The vulnerability has been fixed in:


The patch ensures that authentication cannot proceed unless the token’s signature is successfully verified.

Systems running earlier versions should upgrade immediately.

The Real Lesson

Authentication systems do not fail when cryptography fails.

They fail when assumptions fail.

In this case, the assumption was simple:

If a token decrypts successfully, it must contain a signed payload.

But the JWT specification allows something else.

PlainJWT.

That single edge case allowed an attacker to bypass the authentication system entirely.

The Danger of Silent Authentication Failures

The pac4j vulnerability demonstrates an uncomfortable reality of modern authentication systems:

Security does not fail when cryptography fails.

It fails when control flow fails.

In this case, encryption worked correctly.
The JWT parser behaved exactly as designed.
Every individual component followed its specification.

But a single conditional check allowed authentication to proceed without verifying the token’s signature.

That tiny gap removed the most important security guarantee in the system.

The lesson extends far beyond pac4j.

Any authentication system, whether built on JWT, OAuth, SAML, or custom tokens, must treat signature verification as non-negotiable.

If verification can be skipped, authentication is no longer authentication.

Final Thought

Every authentication system contains a moment where the code decides:

“Is this token trustworthy?”

In pac4j, that decision depended on a single null check.

And that null check removed authentication from the authentication process.

FAQs

What is CVE-2026-29000?

How does the pac4j JWT vulnerability work?

Why is the pac4j JWT vulnerability rated CVSS 10?

What is a PlainJWT?

How can developers fix CVE-2026-29000?

Table of Contents

Start Your 14-Day Free Trial

AI code reviews, security, and quality trusted by modern engineering teams. No credit card required!

Share blog: