Jul 24, 2026·7 min read·18 visits
Auth.js and NextAuth.js fail to bind anti-CSRF cookies to specific OAuth identity providers, allowing attackers to replay session parameters across provider callback boundaries and dynamically link unauthorized identity accounts.
A critical logical security flaw exists in Auth.js (formerly NextAuth.js) where signed anti-CSRF check cookies (state, nonce, and PKCE code_verifier) are not bound to the specific Identity Provider that initiated the authorization flow. In multi-provider environments, this allows an attacker to replay valid, cryptographically signed cookies minted during a flow with one provider against a callback handling a different provider. This vulnerability can lead to session hijacking, identity theft, or unauthorized account linking.
This report analyzes a logical security flaw in Auth.js (formerly NextAuth.js), an open-source authentication library for Node.js environments. The vulnerability, tracked under GHSA-X445-F3H2-J279, belongs to the class of OAuth Provider Confusion (also known as OAuth Mix-up) issues. This flaw manifests in multi-provider configurations where more than one identity provider is registered concurrently.
The attack surface involves the endpoints handling the callback responses from external Identity Providers (IdPs). To maintain session and flow integrity across stateless HTTP redirects, the library relies on cryptographically signed cookies containing state parameters, cryptographic nonces, and Proof Key for Code Exchange (PKCE) verifiers.
Prior to remediation, Auth.js did not bind the generated security parameters to the specific Identity Provider that initiated the flow. This allowed valid, cryptographically sound security tokens minted for a low-trust or attacker-controlled provider to be replayed and successfully validated during callback processing for a high-trust provider.
The root cause of this vulnerability lies in the logical decoupling of anti-CSRF and flow-state parameters from the structural context of their originating Identity Provider. When an authorization sequence begins, Auth.js generates key security parameters to maintain flow security. These parameters include the state (to prevent CSRF), the nonce (to prevent ID token replay), and the code_verifier (for PKCE flow integrity).
To prevent client-side manipulation, the application signs or encrypts these parameters using the JSON Web Encryption (JWE) or JSON Web Token (JWT) standards, keyed by the global NEXTAUTH_SECRET environment variable. The resulting encrypted payloads are set as client-side cookies under generic names such as next-auth.state or next-auth.pkce.code_verifier. These cookie names are shared globally across all authentication providers configured within the application runtime.
During callback handling, the backend routing architecture retrieves these encrypted cookies, decrypts them using the global secret, and validates that their internal values match the incoming parameters provided by the redirect query. The validation routine did not evaluate whether the active cookie had been generated for the specific Identity Provider currently processing the callback. The application only verified the cryptographic integrity and expiration of the parameter itself, leading to a session security gap.
The vulnerability's code-level manifestation and subsequent remediation can be analyzed in both the NextAuth.js v4 branch and the Auth.js v5 (@auth/core) branch. In both instances, the fix introduces a provider binding mechanism into the signed cookie payload.
In the vulnerable v4 implementation inside oauth/checks.ts, the cookie signing process was implemented as follows:
// VULNERABLE: next-auth v4 (oauth/checks.ts)
export async function signCookie(
options: Record<string, any>,
name: string,
value: string,
maxAge: number
) {
return await jwt.encode({
...options.jwt,
maxAge,
token: { value }, // Only the parameter value is encapsulated
salt: name,
})
}The corresponding validation logic did not inspect provider context, allowing any validly signed cookie to pass check validation. The patch introduced in commit 5bca2399a79ba8d116ca5179b4b1ebcd152e7f05 altered both the creation and verification phases:
// PATCHED: next-auth v4 (oauth/checks.ts)
export async function signCookie(
options: Record<string, any>,
name: string,
value: string,
maxAge: number
) {
return await jwt.encode({
...options.jwt,
maxAge,
// The active provider's unique ID is now bound to the token payload
token: { value, provider: options.provider.id },
salt: name,
})
}During consumption, the validation handlers (such as PKCE, State, and Nonce managers) now strictly enforce that the provider ID stored in the decrypted cookie matches the provider executing the callback handler. This is demonstrated in the patched PKCE check:
// PATCHED: next-auth v4 PKCE verification (oauth/checks.ts)
if (!value?.value)
throw new TypeError("PKCE code_verifier value could not be parsed.")
// Strict check to block cross-provider token replay
if (value.provider !== options.provider?.id) {
throw new TypeError(
`${name} cookie was created for a different provider than the one handling the callback.`
)
}In the Auth.js v5 core branch, the fix was implemented in commit 9f7a97fade9b1319bb9ac19fc9828d62e0a2a852 by updating checks.ts. The updated logic enforces the same provider equality matching in the parseCookie utility:
// PATCHED: @auth/core v5 cookie validation (checks.ts)
async function parseCookie(
// ... parameters ...
) {
if (!parsed?.value) throw new Error("Invalid cookie")
// Enforce that the cookie provider ID equals the active callback provider ID
if (parsed.provider !== options.provider?.id) {
throw new Error(
`${name} cookie was created for a different provider than the one handling the callback`
)
}
return parsed.value
}Exploiting GHSA-X445-F3H2-J279 requires an application configuration where at least two OAuth/OIDC providers are configured, and where account linking is either enabled or automatically performed based on user attributes. The attacker must also have a valid account on a low-trust or attacker-controlled identity provider configured within the target system.
The attack sequence proceeds as follows. First, the attacker initiates a legitimate login sequence on the target application utilizing their own low-trust provider credentials. The application generates and sets cryptographically signed security parameter cookies (state, nonce, code verifier) on the attacker's client browser.
Second, the attacker intercepts these state cookies or crafts a social-engineering link. This link is designed to force the victim's browser to execute the callback sequence for a high-trust identity provider (such as a corporate OIDC tenant), but using the security parameter tokens corresponding to the session initiated with the attacker's low-trust provider.
Third, because the target application decrypts the incoming cookies and validates them against the current session data, the signature check succeeds. The application's backend server has no logical record that the cookies were meant for the low-trust flow rather than the high-trust flow. It therefore accepts the authorization code returned by the high-trust IdP, leading to successful authentication or automatic account linking. This effectively binds the attacker's identity provider credentials to the victim's profile, granting the attacker permanent unauthorized access.
The security impact of GHSA-X445-F3H2-J279 is significant, particularly in enterprise or multi-tenant deployments that aggregate multiple identity sources. By manipulating the provider selection during the OAuth authorization flow, an attacker can bypass standard security assumptions associated with high-trust directory services.
The primary consequence of this vulnerability is arbitrary account linking. If an application is configured to automatically link accounts sharing matching email addresses or profiles, an attacker can associate their low-trust identity credentials with an elevated-privilege corporate user account. Once linked, the attacker can authenticate using their low-trust account to access the victim's session resources and administrative controls.
This logical validation bypass has been categorized with a CVSS v3.1 score of 7.5 (High) due to the low complexity of exploitation and the potential for complete compromise of user accounts. It bypasses both the cross-site request forgery protections (CWE-352) and session fixation protections (CWE-384) built into modern OAuth client architectures.
Remediation of the vulnerability requires upgrading the core library dependencies immediately. The fix is backported and available across all major active release channels of the ecosystem.
For teams maintaining NextAuth.js v4 applications, upgrade the dependency to next-auth@4.24.15 or newer. For teams utilizing the Auth.js v5 beta architecture, upgrade to next-auth@5.0.0-beta.32 or newer. Projects consuming the core decoupled core logic should update to @auth/core@0.41.3.
# Upgrade commands for different project configurations
npm install next-auth@4.24.15
npm install next-auth@5.0.0-beta.32
npm install @auth/core@0.41.3> [!IMPORTANT] > Because legacy cookies issued prior to the patch lack the provider property inside their sealed JSON payloads, updating the server-side code will invalidate all active in-flight authentication flows. Users midway through an OAuth redirect during deployment will experience a validation error and must restart their login sequence.
In addition to updating dependencies, teams should audit their multi-provider dynamic registration logic. Ensure that no custom OAuth providers are defined with overlapping, generic, or duplicate ID string properties, as matching provider IDs would still evaluate as equal during validation checks, undermining the security patch.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
next-auth Auth.js | < 4.24.15 | 4.24.15 |
next-auth (v5) Auth.js | < 5.0.0-beta.32 | 5.0.0-beta.32 |
@auth/core Auth.js | < 0.41.3 | 0.41.3 |
| Attribute | Detail |
|---|---|
| Vulnerability ID | GHSA-X445-F3H2-J279 |
| Weakness Class | CWE-384 (Session Fixation) / CWE-352 (CSRF) |
| CVSS v3.1 Score | 7.5 (High) |
| Attack Vector | Network / Remote |
| User Interaction | Required (Targeted redirect execution) |
| Exploit Status | PoC (Verified in test suite) |
| Patched Versions | next-auth >= 4.24.15, next-auth >= 5.0.0-beta.32, @auth/core >= 0.41.3 |
The software reuse of an established session identifier or security parameters allows unauthorized clients to hijack session flows or bypass flow checks.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.