August 20, 2026
Authentication vs. Authorization: How a Misconfigured OAuth Chain Led to an Auth Bypass
When you look at a bug bounty scope with massive enterprise root domains, it’s easy to get overwhelmed. Most hunters will fire off their…

By Priyansh
5 min read
When you look at a bug bounty scope with massive enterprise root domains, it's easy to get overwhelmed. Most hunters will fire off their automated scanners, look for low-hanging fruit like missing security headers, and move on. But sometimes, the most critical vulnerabilities are hiding in the cloud authentication layer of a Single Page Application (SPA) that looks completely impenetrable at first glance.
Today, I want to share a story of how I found a critical authentication bypass on an internal enterprise aviation operations web application. We'll go step-by-step through the recon, the dead ends, the OAuth deep dive, and the exact moment when a misconfigured cloud identity layer exposed a fatal flaw in the application's access control.
Let's get into it.
Phase 1: The Recon and the "Boring" Target
I started my recon like any other engagement: running subfinder to enumerate subdomains and piping the results into httpx to grab HTTP status codes, titles, and tech stacks.
Most of the scope returned standard CMS installations, API gateways returning 401 Unauthorized, or redirect loops. But one subdomain caught my eye. Let's call it target.com.
When I hit the root path, I got a 200 OK with a completely blank HTML body:
<!doctype html>
<html lang="en"><head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Internal Ops Portal</title><script defer="defer" src="/static/js/main.abc123.js">
</script><link href="/static/css/main.def456.css" rel="stylesheet">
</head><body><div id="root"></div></body></html><!doctype html>
<html lang="en"><head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Internal Ops Portal</title><script defer="defer" src="/static/js/main.abc123.js">
</script><link href="/static/css/main.def456.css" rel="stylesheet">
</head><body><div id="root"></div></body></html>This was a React Single Page Application. SPAs are notoriously difficult to test because all the routing and business logic happen client-side, and the backend API is often hidden behind a reverse proxy that returns the index.html file for any unknown path (a catch-all route).
I tried probing common API paths like /api/v1/users, /api/docs, and /swagger-ui.html. Every single request returned a 200 OK with text/html. The reverse proxy was aggressively swallowing all requests and serving the SPA HTML wrapper. I was effectively blind to the backend.
Phase 2: The Azure Easy Auth Discovery
If I couldn't find the API through brute force, I had to understand the application's authentication architecture. Since this was an Azure-hosted SPA, I checked for the telltale signs of Azure App Service Easy Auth (the built-in authentication/authorization module).
I hit the Easy Auth diagnostic endpoint:
curl -s -i -L -H “Accept: application/json” https://target.com/.auth/mecurl -s -i -L -H “Accept: application/json” https://target.com/.auth/meThe server replied with a 200 OK:
{
"clientPrincipal": null
}{
"clientPrincipal": null
}This was a massive finding. In a properly locked-down Easy Auth deployment, hitting /.auth/me unauthenticated yields a 401 Unauthorized or a redirect to a login provider. Returning 200 with a null principal confirms that unauthenticatedClientAction is set to AllowAnonymous.
The application was letting anyone load the SPA shell and hit the authentication endpoints without being logged in. But that alone isn't a full breach — it just means the frontend is public. I needed to see what identity providers were configured, and if I could sneak through them.
Phase 3: The Multi-Provider Deep Dive
Easy Auth exposes login routes at /.auth/login/<provider>. I decided to trace the OAuth redirects to see what identity providers the app was trusting. I ran a series of curl commands, following the redirects to see where an attacker would end up.
First, Microsoft Azure AD:
curl -s -L — max-redirs 5 -o /dev/null -w “%{url_effective}\n” https://target.com/.auth/login/aadcurl -s -L — max-redirs 5 -o /dev/null -w “%{url_effective}\n” https://target.com/.auth/login/aadThe final URL stopped at: https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=d414ee2d-73e5-4e5b-bb16-03ef55fea597...
I immediately spotted the vulnerability. The Azure AD authorization endpoint used /common/ as the tenant. This means Azure AD will accept an identity token from any Microsoft tenant — outlook.com, hotmail.com, personal MSA accounts, or any stranger's M365 tenant. It wasn't restricted to the corporate tenant.
Next, I checked Google:
curl -s -L — max-redirs 5 -o /dev/null -w “%{url_effective}\n” https://target.com/.auth/login/googlecurl -s -L — max-redirs 5 -o /dev/null -w “%{url_effective}\n” https://target.com/.auth/login/googleThe redirect pointed to Google's OAuth flow, but again, I noticed something missing. The authorization URL contained scope=openid+profile+email, but no hd (hosted domain) parameter. Google's hd parameter restricts which Workspace domain can complete the consent. Without it, any free @gmail.com account passes the check.
Finally, I checked GitHub:
curl -s -L --max-redirs 5 -o /dev/null -w "%{url_effective}\n" https://target.com/.auth/login/githubcurl -s -L --max-redirs 5 -o /dev/null -w "%{url_effective}\n" https://target.com/.auth/login/githubPhase 4: The Smoking Gun
The authentication layer was wide open. Anyone with a free Google, GitHub, or Microsoft account could complete an OAuth round-trip and get a valid Easy Auth session.
But the critical question remained: Does the backend application trust this Easy Auth session blindly, or does it validate the identity provider and email domain?
When Easy Auth authenticates a user, it injects a base64-encoded JSON Web Token into the X-MS-CLIENT-PRINCIPAL header before routing the request to the backend application code. The backend uses this header to identify the user.
If the backend developers assumed that any valid X-MS-CLIENT-PRINCIPAL header meant the user was an internal employee (because they assumed only internal AAD could generate it), then the multi-provider misconfiguration wasn't just an auth misconfiguration — it was a full authentication bypass. We thought the backend might have strict role-based access control that would block us even if we got a session. We were wrong.
Phase 5: The Exploit
There was only one way to find out. I created a completely fresh, throwaway Google account (test.bounty123@gmail.com).
- I navigated to
[https://target.com/.auth/login/google](https://target.com/.auth/login/google.). - I completed the OAuth consent flow using my throwaway Gmail account.
- I was redirected back to the application, fully logged in.
I immediately checked /.auth/me again. Instead of null, it returned a fully populated clientPrincipal containing my Gmail address, with the identityProvider listed as google.
The Easy Auth layer had minted a valid session for my throwaway account.
I navigated to the application's main dashboard. The SPA loaded fully. Because the backend implicitly trusted the Easy Auth-injected X-MS-CLIENT-PRINCIPAL header to authorize access to internal operational features — without verifying if the preferred_username ended in @corp.com or if the identityProvider was azureactivedirectory — my free Gmail account was treated as an authenticated internal user.
I had bypassed enterprise SSO entirely. I gained full access to the internal aviation operations web application, exposing sensitive operational data, all because the developers left the social login gates wide open and failed to enforce domain validation on the backend.
I reported the vulnerability through the bug bounty program. It was triaged as a High severity issue, and the vendor quickly patched it by restricting the AAD tenant, removing the Google/GitHub providers, and enforcing backend validation of the identity provider and email domain. The bounty? A solid 1000 Euros.
The Lesson
This vulnerability wasn't caused by a complex memory corruption or a zero-day. It was caused by a fundamental breakdown in cloud identity architecture.
The developers likely enabled multiple social providers to make testing or third-party access easier, but they forgot to restrict the tenant or hosted domains. When the code went to production, the presence of an Easy Auth session became the single source of truth for authorization, ignoring who the user actually was.
Takeaways for Hunters:
- Never ignore
/.auth/me: If an Azure app exposes Easy Auth endpoints, probe them. A 200 OK with anullprincipal means anonymous access is on. - Trace the OAuth Redirects: Don't just look at the login page UI. Follow the HTTP redirects. Look for
tenant=commonin Azure AD, or the absence ofhd=in Google OAuth. These are silent misconfigurations that allow anyone to mint a token. - Authentication vs. Authorization: Just because a user can "log in" doesn't mean they are authorized. If you find an open identity provider, complete the flow. You never know when the backend implicitly trusts the frontend's identity layer.