BTC
ETH
SOL
BNB
GOLD
XRP
DOGE
ADA
Back to home
Security

[HIGH] Security Advisory: Laravel Passport: TokenGuard Authenticates Unrelated User for Client Credentials Tokens (laravel/passport)

Laravel Passport, the popular OAuth2 server for Laravel apps, has a high-severity authentication bypass vulnerability in its TokenGuard.

Laravel Passport, the popular OAuth2 server for Laravel apps, has a high-severity authentication bypass vulnerability in its TokenGuard. Attackers with a client credentials token can impersonate any user whose ID matches the client ID. This hits versions before 13.7.1, but only if you disable UUIDs for clients by setting Passport::$clientUuids = false. By default, clients use UUIDs, so integer collisions don’t happen—yet thousands of apps might have tweaked this for simplicity.

The root cause traces to the underlying league/oauth2-server library. For client credentials grants—machine-to-machine auth with no user involved—it sets the JWT’s sub claim to the client ID. Passport’s TokenGuard grabs this sub value and feeds it straight to User::retrieveById() without checking if it’s a valid user identifier. Boom: if a client ID (say, 123) matches a user’s primary key (also 123), the token authenticates as that user.

Trigger Conditions and Scope

This doesn’t strike blindly. Passport defaults to UUID clients, which are 36-character strings unlikely to collide with auto-incrementing user IDs. You must explicitly set Passport::$clientUuids = false to use integer client IDs. Documentation even warns about this exact risk for client credentials tokens. The EnsureClientIsResourceOwner middleware, meant to enforce client-only auth, fails here too—it can’t reliably distinguish client tokens from user ones when IDs overlap.

Laravel Passport powers OAuth2 in countless APIs: e-commerce backends, SaaS platforms, mobile apps. Client credentials suit service accounts, like a cron job hitting your API or third-party integrations. If your app runs this config—common in legacy setups or when devs prioritize sequential IDs—you expose user-level access via a simple token. No exploits needed beyond generating a client credentials token with a colliding ID.

Prevalence? Laravel holds 2-3% of PHP market share per W3Techs, Passport tags over 100k GitHub downloads monthly. GitHub issues like #1900 surfaced this in October 2024, with PRs #1901/#1902 landing the fix. The league/oauth2-server comment from 2024 echoes long-known OAuth2 quirks. Skeptical take: this feels like an oversight in edge-case handling, not malice. But it underscores why defaults matter—UUIDs exist to dodge exactly these ID namespace clashes.

Fixes, Workarounds, and Implications

Upgrade to Passport 13.7.1 or later. The patch validates the sub claim properly, rejecting non-user IDs in user contexts. Laravel 13.x users get this via composer update laravel/passport. Older branches? Check compatibility; Passport supports Laravel 10-13.

No upgrade? Disable client credentials grants entirely. Revoke exposed tokens and audit client/user ID overlaps with a query like:

SELECT c.id, u.id FROM oauth_clients c JOIN users u ON c.id = u.id WHERE c.user_id IS NULL;

Switch to UUID clients if possible—migrate existing ones via artisan commands or scripts.

Why this matters: auth bypasses rank among top CVEs. Here, a M2M token escalates to user privileges—reading profiles, orders, or worse if RBAC is loose. In a real breach, attackers enumerate clients, craft colliding IDs, and pivot. Financial apps? Instant PII dump. APIs with weak scopes amplify damage. Broader lesson: OAuth2’s flexibility breeds pitfalls; always isolate client/user namespaces. Laravel’s docs flag this, but devs skip them. Post-patch, audit your setup—don’t assume defaults save you.

Track references for full dive:

Bottom line: patch now if affected. This exposes how thin config choices unravel security. Stay vigilant—OAuth ain’t set-it-and-forget-it.

April 9, 2026 · 3 min · 12 views · Source: GitHub Security

Related