[{"data":1,"prerenderedAt":759},["ShallowReactive",2],{"navigation":3,"/blog/microsoft-sso-misconfiguration-credential-hijacking":70,"/blog/microsoft-sso-misconfiguration-credential-hijacking-surround":754},[4],{"title":5,"path":6,"stem":7,"children":8,"page":69},"Blog","/blog","blog",[9,13,17,21,25,29,33,37,41,45,49,53,57,61,65],{"title":10,"path":11,"stem":12},"The AI Developer Trap: When Copilots Become Crutches","/blog/ai-developer-trap","blog/ai-developer-trap",{"title":14,"path":15,"stem":16},"The CLOUD Act: When American Law Enforcement Can Access Your Data Anywhere","/blog/cloud-act-cross-border-data-sovereignty","blog/cloud-act-cross-border-data-sovereignty",{"title":18,"path":19,"stem":20},"The Container Revolution: From Docker's Disruption to the OCI Framework","/blog/container-evolution-docker-to-oci","blog/container-evolution-docker-to-oci",{"title":22,"path":23,"stem":24},"Continuous Integration: Building Enterprise-Grade CI Pipelines","/blog/continuous-integration-enterprise-guide","blog/continuous-integration-enterprise-guide",{"title":26,"path":27,"stem":28},"Flutter: 2026 Application Development on Steroids","/blog/flutter-2026-application-development-on-steroids","blog/flutter-2026-application-development-on-steroids",{"title":30,"path":31,"stem":32},"Building Production-Ready Go APIs in Scratch Containers","/blog/golang-scratch-containers-production-apis","blog/golang-scratch-containers-production-apis",{"title":34,"path":35,"stem":36},"Infrastructure as Code: The Terraform Supremacy and Its Challengers","/blog/infrastructure-as-code-terraform-comparison","blog/infrastructure-as-code-terraform-comparison",{"title":38,"path":39,"stem":40},"The Invisible Skeleton Key: How a Microsoft SSO Misconfiguration Can Hand Attackers Your Users' Accounts","/blog/microsoft-sso-misconfiguration-credential-hijacking","blog/microsoft-sso-misconfiguration-credential-hijacking",{"title":42,"path":43,"stem":44},"SDET: The Unsung Heroes of Enterprise Software Quality","/blog/sdet-automated-testing-enterprises","blog/sdet-automated-testing-enterprises",{"title":46,"path":47,"stem":48},"Christmas Came Early: Security Misconfigurations Jump to #2 on OWASP Top 10","/blog/security-misconfigurations-owasp-top-2","blog/security-misconfigurations-owasp-top-2",{"title":50,"path":51,"stem":52},"TLS Protocol Versions: Understanding 1.0, 1.1, 1.2, and 1.3","/blog/tls-protocol-versions-guide","blog/tls-protocol-versions-guide",{"title":54,"path":55,"stem":56},"Wails: Building Desktop Applications with Go and Web Technologies","/blog/wails-go-desktop-applications-web-technologies","blog/wails-go-desktop-applications-web-technologies",{"title":58,"path":59,"stem":60},"Waterfall, Agile, Scrum, and the SDLC: Breaking Down What Actually Matters","/blog/waterfall-agile-scrum-sdlc-breakdown","blog/waterfall-agile-scrum-sdlc-breakdown",{"title":62,"path":63,"stem":64},"When the Web Goes Dark: The Hidden Fragility of Centralized Infrastructure","/blog/when-the-web-goes-dark-infrastructure-centralization","blog/when-the-web-goes-dark-infrastructure-centralization",{"title":66,"path":67,"stem":68},"Zero-Trust Network Architecture: Implementation Guide","/blog/zero-trust-network-architecture","blog/zero-trust-network-architecture",false,{"id":71,"title":38,"author":72,"body":74,"date":738,"description":739,"extension":740,"image":741,"meta":742,"minRead":440,"navigation":248,"path":39,"seo":752,"stem":40,"__hash__":753},"blog/blog/microsoft-sso-misconfiguration-credential-hijacking.md",{"name":73},"Julian Morley",{"type":75,"value":76,"toc":728},"minimark",[77,86,89,92,95,100,106,114,117,121,124,127,133,136,172,193,199,215,218,222,225,273,276,329,332,340,343,347,350,356,359,362,366,381,384,478,481,497,500,518,528,534,540,546,550,553,556,609,612,618,627,631,638,644,647,653,659,662,665,669,682,693,703,713,724],[78,79,80,81,85],"p",{},"A few years ago I was working with a digital signage company—the kind of business that puts interactive kiosks in shopping centres and corporate lobbies. They had built a reasonably polished SaaS portal where customers could manage their displays, upload content, and configure schedules. They had also, sensibly they thought, integrated Microsoft Entra ID (then still called Azure AD) as an SSO option. The familiar blue button appeared on their login page: ",[82,83,84],"em",{},"Log in with Microsoft",".",[78,87,88],{},"Then a bug bounty hunter filed a report against their demo environment. The finding was marked critical. Did the engineering team patch the bug? Not sure because most companies still run the \"if it aint broke, don't fix it\" mindset.",[78,90,91],{},"The vulnerability was not exotic. It did not require zero-days, social engineering, or physical access. It required knowing a target's email address, creating a free Microsoft account with that same address, and clicking the blue button. That was it. The attacker walked straight into the victim's account—including whatever roles and content configuration that account had accumulated.",[78,93,94],{},"It is, in my experience, one of the most common and least discussed authentication vulnerabilities in enterprise SaaS applications—and it lives in a line of code that most developers never think twice about.",[96,97,99],"h2",{"id":98},"how-oauth-20-and-oidc-work-with-microsoft-entra-id","How OAuth 2.0 and OIDC Work with Microsoft Entra ID",[78,101,102,103,105],{},"To understand the vulnerability, you first need a clear picture of the flow. When a user clicks ",[82,104,84],{},", the application redirects them to Microsoft's authorization endpoint. After the user authenticates, Microsoft issues an authorization code—a short-lived, one-time credential—and redirects the browser back to the application's registered callback URI. The application then exchanges that code for tokens at Microsoft's token endpoint (Microsoft, 2026).",[78,107,108,109,113],{},"The payload that matters most for identity is the ",[110,111,112],"strong",{},"ID token","—a signed JSON Web Token (JWT) that describes the authenticated user. It contains \"claims\": key-value pairs that assert facts about the user. The application's job, once it has validated the token's signature and expiry, is to look at those claims, figure out who just logged in, and either create a local session or map the incoming identity to an existing user account.",[78,115,116],{},"That mapping step is where the vulnerability lives.",[96,118,120],{"id":119},"two-claims-that-never-lie-and-several-that-do","Two Claims That Never Lie, and Several That Do",[78,122,123],{},"Microsoft's ID token contains a rich set of claims, but they are not all equal in terms of stability. Some claims describe the user's current state in the directory. Others are computed, immutable identifiers that are guaranteed to be permanent and unique.",[78,125,126],{},"Microsoft's own documentation is explicit on this point:",[128,129,130],"blockquote",{},[78,131,132],{},"\"When identifying a user, it's critical to use information that remains constant and unique across time. Legacy applications sometimes use fields like the email address, phone number, or UPN. All of these fields can change over time, and can also be reused over time.\" (Microsoft, 2025a)",[78,134,135],{},"The claims you should trust for identity correlation are:",[137,138,139,153,164],"ul",{},[140,141,142,148,149,152],"li",{},[110,143,144],{},[145,146,147],"code",{},"oid"," (Object ID): A GUID that uniquely identifies the user account within the Entra ID tenant. It is immutable—it does not change if the user changes their name, their email, or their username. It persists even if the user's account is disabled and re-enabled. Microsoft Graph uses this same value as the ",[145,150,151],{},"id"," property for a user object.",[140,154,155,160,161,163],{},[110,156,157],{},[145,158,159],{},"sub"," (Subject): Also immutable, but deliberately scoped to a specific application. Two different applications will see different ",[145,162,159],{}," values for the same user, making it suitable for per-app identity storage without cross-app correlation.",[140,165,166,171],{},[110,167,168],{},[145,169,170],{},"tid"," (Tenant ID): Identifies which Entra ID tenant the token came from. Critical for multi-tenant applications.",[78,173,174,175,178,179,178,182,185,186,189,190,192],{},"Everything else—in particular ",[145,176,177],{},"email",", ",[145,180,181],{},"preferred_username",[145,183,184],{},"upn",", and the ",[145,187,188],{},"name"," claim—is mutable. A user can change their display name. An IT administrator can reassign an email address to a new employee after the previous one leaves. The ",[145,191,181],{}," in v2.0 tokens is documented explicitly as something that \"might change over time\" and \"can't be used to make authorization decisions\" (Microsoft, 2025a).",[78,194,195,196,198],{},"The practical implication is straightforward: if your application stores a user record keyed on ",[145,197,177],{}," and performs its lookup like this—",[200,201,206],"pre",{"className":202,"code":203,"language":204,"meta":205,"style":205},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","SELECT * FROM users WHERE email = :email\n","sql","",[145,207,208],{"__ignoreMap":205},[209,210,213],"span",{"class":211,"line":212},"line",1,[209,214,203],{},[78,216,217],{},"—then your authentication is only as secure as the email address itself.",[96,219,221],{"id":220},"the-pre-registration-attack","The Pre-Registration Attack",[78,223,224],{},"Here is the attack in concrete terms. Assume your application's SSO callback logic looks roughly like this (pseudocode):",[200,226,230],{"className":227,"code":228,"language":229,"meta":205,"style":205},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","claims = validate_and_decode_id_token(token)\nuser = db.find_user_by_email(claims[\"email\"])\n\nif user is None:\n    user = db.create_user(email=claims[\"email\"], ...)\n\nsession.login(user)\n","python",[145,231,232,237,243,250,256,262,267],{"__ignoreMap":205},[209,233,234],{"class":211,"line":212},[209,235,236],{},"claims = validate_and_decode_id_token(token)\n",[209,238,240],{"class":211,"line":239},2,[209,241,242],{},"user = db.find_user_by_email(claims[\"email\"])\n",[209,244,246],{"class":211,"line":245},3,[209,247,249],{"emptyLinePlaceholder":248},true,"\n",[209,251,253],{"class":211,"line":252},4,[209,254,255],{},"if user is None:\n",[209,257,259],{"class":211,"line":258},5,[209,260,261],{},"    user = db.create_user(email=claims[\"email\"], ...)\n",[209,263,265],{"class":211,"line":264},6,[209,266,249],{"emptyLinePlaceholder":248},[209,268,270],{"class":211,"line":269},7,[209,271,272],{},"session.login(user)\n",[78,274,275],{},"This pattern is extremely common. It is also what the digital signage company had. And here is the exact sequence an attacker exploits:",[277,278,279,289,304,319],"ol",{},[140,280,281,284,285,288],{},[110,282,283],{},"Reconnaissance."," The attacker knows that ",[145,286,287],{},"alice@contoso.com"," has an administrative account in the target application. Her address might be visible in a company directory, found through LinkedIn, or simply guessed.",[140,290,291,294,295,297,298,300,301,303],{},[110,292,293],{},"Account pre-registration."," The attacker registers a personal Microsoft account (or a free Azure AD account) using ",[145,296,287],{}," as the email address. Microsoft personal accounts allow this. Importantly, the ",[145,299,147],{}," in this new account is completely different from Alice's real corporate ",[145,302,147],{},"—it belongs to the attacker.",[140,305,306,309,310,312,313,316,317,85],{},[110,307,308],{},"SSO initiation."," The attacker visits the target application and clicks ",[82,311,84],{},", authenticating with their freshly created account. Microsoft issues a valid, properly signed ID token containing ",[145,314,315],{},"email: alice@contoso.com","—and their own, different ",[145,318,147],{},[140,320,321,324,325,328],{},[110,322,323],{},"Identity hijack."," The application validates the token signature correctly. The signature is genuine. But then it queries ",[145,326,327],{},"users WHERE email = 'alice@contoso.com'",", finds Alice's legitimate account, and creates a session for it. The attacker is now logged in as Alice—with all of Alice's permissions, roles, and data.",[78,330,331],{},"No credentials were stolen. No phishing occurred. The identity provider did everything correctly. The flaw was entirely in how the application decided to correlate an authenticated identity with a local user record.",[78,333,334,335,178,337,339],{},"The attack works equally well if the application uses ",[145,336,181],{},[145,338,188],{},", or a display name field instead of email. Any claim that the attacker can influence or predict—and that the target application treats as the primary key—becomes a vector.",[78,341,342],{},"Carmel (2023) documented analogous patterns in OAuth implementations at Grammarly, Vidio, and Bukalapak, where access token reuse across applications enabled account takeover for hundreds of millions of users. The root cause in each case was the same conceptual error: applications trusted user-supplied or cross-context identity attributes rather than verifying a canonical, application-specific identifier.",[96,344,346],{"id":345},"why-this-compounds-roles-permissions-and-privilege-escalation","Why This Compounds: Roles, Permissions, and Privilege Escalation",[78,348,349],{},"The scenario above describes taking over an existing ordinary account. When combined with role-based access control, the blast radius grows considerably.",[78,351,352,353,355],{},"In many SaaS applications, accounts are provisioned—and assigned roles—before the user ever logs in. An IT administrator grants ",[145,354,287],{}," the \"Content Manager\" or \"Billing Administrator\" role in the application's database, then tells Alice to log in. The application is designed to welcome her on first SSO login by finding the pre-created record using her email.",[78,357,358],{},"This provisioned-but-not-yet-logged-in account is exactly the state an attacker wants to find. By striking before Alice, the attacker inherits all the roles that were pre-assigned, without needing to know what those roles are. In the digital signage context this meant access to display configurations and content. In a financial or HR SaaS product the stakes are considerably higher.",[78,360,361],{},"Even without pre-existing role assignment, an attacker who controls an account can accumulate privileges over time—or wait for the real user to be granted escalated access—and then re-exploit the trust at a more opportune moment.",[96,363,365],{"id":364},"the-fix-anchoring-identity-on-what-cannot-be-changed","The Fix: Anchoring Identity on What Cannot Be Changed",[78,367,368,369,371,372,374,375,377,378,380],{},"The remediation is not complicated, but it requires deliberate intent. The core principle from Microsoft's documentation is: use ",[145,370,147],{}," and ",[145,373,170],{}," together as the canonical compound key (Microsoft, 2025a). Not ",[145,376,177],{},". Not ",[145,379,184],{},". Not a display name.",[78,382,383],{},"A corrected version of the lookup above looks like this:",[200,385,387],{"className":227,"code":386,"language":229,"meta":205,"style":205},"claims = validate_and_decode_id_token(token)\noid = claims[\"oid\"]\ntid = claims[\"tid\"]\n\nuser = db.find_user_by_oid_and_tid(oid, tid)\n\nif user is None:\n    # New user — create a record anchored on oid+tid\n    user = db.create_user(\n        oid=oid,\n        tid=tid,\n        email=claims.get(\"email\"),   # store for display, never for lookup\n        display_name=claims.get(\"name\"),\n        ...\n    )\n\nsession.login(user)\n",[145,388,389,393,398,403,407,412,416,420,426,432,438,444,450,456,462,468,473],{"__ignoreMap":205},[209,390,391],{"class":211,"line":212},[209,392,236],{},[209,394,395],{"class":211,"line":239},[209,396,397],{},"oid = claims[\"oid\"]\n",[209,399,400],{"class":211,"line":245},[209,401,402],{},"tid = claims[\"tid\"]\n",[209,404,405],{"class":211,"line":252},[209,406,249],{"emptyLinePlaceholder":248},[209,408,409],{"class":211,"line":258},[209,410,411],{},"user = db.find_user_by_oid_and_tid(oid, tid)\n",[209,413,414],{"class":211,"line":264},[209,415,249],{"emptyLinePlaceholder":248},[209,417,418],{"class":211,"line":269},[209,419,255],{},[209,421,423],{"class":211,"line":422},8,[209,424,425],{},"    # New user — create a record anchored on oid+tid\n",[209,427,429],{"class":211,"line":428},9,[209,430,431],{},"    user = db.create_user(\n",[209,433,435],{"class":211,"line":434},10,[209,436,437],{},"        oid=oid,\n",[209,439,441],{"class":211,"line":440},11,[209,442,443],{},"        tid=tid,\n",[209,445,447],{"class":211,"line":446},12,[209,448,449],{},"        email=claims.get(\"email\"),   # store for display, never for lookup\n",[209,451,453],{"class":211,"line":452},13,[209,454,455],{},"        display_name=claims.get(\"name\"),\n",[209,457,459],{"class":211,"line":458},14,[209,460,461],{},"        ...\n",[209,463,465],{"class":211,"line":464},15,[209,466,467],{},"    )\n",[209,469,471],{"class":211,"line":470},16,[209,472,249],{"emptyLinePlaceholder":248},[209,474,476],{"class":211,"line":475},17,[209,477,272],{},[78,479,480],{},"The email and display name can still be stored and displayed—they are useful for human-readable UI. But they must never be used for the security-critical lookup that determines which account receives the authenticated session.",[78,482,483,484,486,487,489,490,492,493,496],{},"For multi-tenant applications, the ",[145,485,170],{}," is essential because ",[145,488,147],{}," values are scoped per tenant. The same physical person authenticated across two different Entra ID tenants will have two different ",[145,491,147],{}," values—by design, to prevent cross-tenant tracking (Microsoft, 2025a). Using ",[145,494,495],{},"oid + tid"," as a compound key correctly models this.",[78,498,499],{},"Beyond the claims check, there are several complementary controls worth verifying in any SSO implementation:",[78,501,502,509,510,513,514,517],{},[110,503,504,505,508],{},"Validate the ",[145,506,507],{},"iss"," (issuer) claim."," For single-tenant applications this should be locked to ",[145,511,512],{},"https://login.microsoftonline.com/{your-tenant-id}/v2.0",". For multi-tenant applications, the issuer validation is more nuanced, but you should at minimum ensure you are rejecting tokens from tenants you do not intend to serve. Accepting tokens from ",[145,515,516],{},"https://login.microsoftonline.com/common/v2.0"," without further validation means any Entra ID tenant in the world can produce a valid token for your application.",[78,519,520,527],{},[110,521,522,523,526],{},"Validate ",[145,524,525],{},"aud"," (audience)."," The ID token's audience claim must match your application's Client ID. This prevents token reuse across applications—the class of attack documented by Salt Security's research (Carmel, 2023).",[78,529,530,533],{},[110,531,532],{},"Use PKCE."," The Proof Key for Code Exchange extension prevents authorization code interception attacks in public clients. Microsoft's documentation marks it as required for single-page applications and recommended for all client types (Microsoft, 2026). If you are using an older implementation that omits it, add it.",[78,535,536,539],{},[110,537,538],{},"Use Microsoft's identity libraries."," MSAL (Microsoft Authentication Library) handles token acquisition, signature validation, and claim extraction correctly by default. Hand-rolled JWT parsing is where subtle mistakes—like trusting unsigned token segments, or skipping expiry checks—tend to appear. Stepankin (2021) catalogued a number of these subtle misconfigurations across open-source OAuth server implementations; the common thread was developers re-implementing protocol logic that the library would have handled correctly.",[78,541,542,545],{},[110,543,544],{},"Keep libraries updated."," OAuth and OIDC libraries occasionally patch security issues in their validation logic. Pinning dependency versions without monitoring for updates is a quiet way to inherit known vulnerabilities.",[96,547,549],{"id":548},"auditing-your-own-application","Auditing Your Own Application",[78,551,552],{},"If you maintain an application with Microsoft SSO integration that you did not write from scratch—or one that has grown through many developers over time—it is worth doing a targeted search for this specific pattern.",[78,554,555],{},"Start in the token callback or OIDC middleware handler. Find the code path that runs after a successful authentication and look for the first database interaction. Ask one question: what field is the query filtering on?",[200,557,559],{"className":227,"code":558,"language":229,"meta":205,"style":205},"# Bad — any of these as a lookup key\ndb.query(\"SELECT * FROM users WHERE email = ?\", claims[\"email\"])\ndb.query(\"SELECT * FROM users WHERE username = ?\", claims[\"preferred_username\"])\ndb.query(\"SELECT * FROM users WHERE display_name = ?\", claims[\"name\"])\n\n# Also bad — using a custom claim that might be set by the calling app\ndb.query(\"SELECT * FROM users WHERE external_id = ?\", claims[\"custom_attr\"])\n\n# Good\ndb.query(\"SELECT * FROM users WHERE oid = ? AND tid = ?\", claims[\"oid\"], claims[\"tid\"])\n",[145,560,561,566,571,576,581,585,590,595,599,604],{"__ignoreMap":205},[209,562,563],{"class":211,"line":212},[209,564,565],{},"# Bad — any of these as a lookup key\n",[209,567,568],{"class":211,"line":239},[209,569,570],{},"db.query(\"SELECT * FROM users WHERE email = ?\", claims[\"email\"])\n",[209,572,573],{"class":211,"line":245},[209,574,575],{},"db.query(\"SELECT * FROM users WHERE username = ?\", claims[\"preferred_username\"])\n",[209,577,578],{"class":211,"line":252},[209,579,580],{},"db.query(\"SELECT * FROM users WHERE display_name = ?\", claims[\"name\"])\n",[209,582,583],{"class":211,"line":258},[209,584,249],{"emptyLinePlaceholder":248},[209,586,587],{"class":211,"line":264},[209,588,589],{},"# Also bad — using a custom claim that might be set by the calling app\n",[209,591,592],{"class":211,"line":269},[209,593,594],{},"db.query(\"SELECT * FROM users WHERE external_id = ?\", claims[\"custom_attr\"])\n",[209,596,597],{"class":211,"line":422},[209,598,249],{"emptyLinePlaceholder":248},[209,600,601],{"class":211,"line":428},[209,602,603],{},"# Good\n",[209,605,606],{"class":211,"line":434},[209,607,608],{},"db.query(\"SELECT * FROM users WHERE oid = ? AND tid = ?\", claims[\"oid\"], claims[\"tid\"])\n",[78,610,611],{},"Custom claims deserve particular attention in enterprise integrations. Applications sometimes add custom attributes to the Entra ID token via optional claims configuration or through a claims-mapping policy. These can be useful, but if they contain values derived from directory attributes—especially ones that can be modified by the user or a delegated admin—they carry the same risk as built-in mutable claims.",[78,613,614,615,617],{},"Also review your user provisioning flow. If your application supports just-in-time provisioning (creating a user record on first login), verify that the provisioned record's primary key is the ",[145,616,147],{},"—not the email, even if email is the address used to send the welcome notification. A mismatch here is exactly what makes the pre-registration attack possible.",[78,619,620,621,623,624,626],{},"Finally, check the flow for pre-provisioned accounts—records created by an admin before the user's first login. If those records are keyed on email and a matching ",[145,622,147],{}," is never stored until the first actual login, there is a window during which the pre-registration attack applies. The simplest remediation is to store the ",[145,625,147],{}," at provisioning time (retrieved via Microsoft Graph using the admin's access token) rather than waiting for the user's first login to populate it.",[96,628,630],{"id":629},"the-broader-pattern","The Broader Pattern",[78,632,633,634,637],{},"What strikes me most about this class of vulnerability is how naturally it emerges from good intentions. The developer who wrote the ",[145,635,636],{},"find_user_by_email"," lookup was almost certainly thinking: \"Email is how we identify people. Microsoft has verified this person's email. Therefore, looking them up by email makes sense.\"",[78,639,640,641,643],{},"The reasoning is coherent, and in a world where identity providers guaranteed email address uniqueness and immutability, it would even be correct. But email addresses are not uniquely owned—they can be shared across providers, reassigned after account deletion, or registered on personal Microsoft accounts pointing to any address the registrant claims. The ",[145,642,147],{}," exists precisely because the identity provider understands its own data model better than any downstream attribute: it is the primary key for a user object in Entra ID's own database.",[78,645,646],{},"Microsoft makes this explicit in their claims reference: \"Your application mustn't use human-readable data to identify a user\" (Microsoft, 2025a). That guidance has been in the documentation for years. It still gets missed.",[78,648,649,650,652],{},"The digital signage company fixed the bug within hours of the proof-of-concept. The patch was a handful of lines. The pre-existing user records were migrated to store ",[145,651,495],{}," as the lookup key, and the callback handler was updated accordingly. No public disclosure was required because the vulnerability was found on a demo environment with no real customer data.",[78,654,655,656,658],{},"But the reason it existed in the first place—a reasonable-seeming assumption about how identity works, made early in the project and never revisited—is identical to the reason it exists in other applications today. If you have a ",[82,657,84],{}," button, and you have not explicitly verified which claim your user lookup queries against, this is worth fifteen minutes of your time.",[78,660,661],{},"The blue button is not the attack surface. The line of code that runs after it is.",[663,664],"hr",{},[96,666,668],{"id":667},"references","References",[78,670,671,672,675,676],{},"Carmel, A. (2023, October 24). ",[82,673,674],{},"Oh-auth — abusing OAuth to take over millions of accounts",". Salt Security. ",[677,678,679],"a",{"href":679,"rel":680},"https://salt.security/blog/oh-auth-abusing-oauth-to-take-over-millions-of-accounts",[681],"nofollow",[78,683,684,685,688,689],{},"Microsoft. (2025a). ",[82,686,687],{},"ID token claims reference",". Microsoft Entra documentation. ",[677,690,691],{"href":691,"rel":692},"https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference",[681],[78,694,695,696,688,699],{},"Microsoft. (2025b). ",[82,697,698],{},"Security best practices for application properties in Microsoft Entra ID",[677,700,701],{"href":701,"rel":702},"https://learn.microsoft.com/en-us/entra/identity-platform/security-best-practices-for-app-registration",[681],[78,704,705,706,688,709],{},"Microsoft. (2026). ",[82,707,708],{},"Microsoft identity platform and OAuth 2.0 authorization code flow",[677,710,711],{"href":711,"rel":712},"https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow",[681],[78,714,715,716,719,720],{},"Stepankin, M. (2021, March 24). ",[82,717,718],{},"Hidden OAuth attack vectors",". PortSwigger Research. ",[677,721,722],{"href":722,"rel":723},"https://portswigger.net/research/hidden-oauth-attack-vectors",[681],[725,726,727],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":205,"searchDepth":239,"depth":239,"links":729},[730,731,732,733,734,735,736,737],{"id":98,"depth":239,"text":99},{"id":119,"depth":239,"text":120},{"id":220,"depth":239,"text":221},{"id":345,"depth":239,"text":346},{"id":364,"depth":239,"text":365},{"id":548,"depth":239,"text":549},{"id":629,"depth":239,"text":630},{"id":667,"depth":239,"text":668},"2026-04-14T00:00:00.000Z","When an application uses a mutable claim—like email or display name—instead of the immutable oid to identify users after OAuth login, a single pre-registered account can become a skeleton key to anyone's session. This is the story of how I first encountered this vulnerability, and what every developer implementing \"Log in with Microsoft\" needs to know.","md","https://images.unsplash.com/photo-1550751827-4bd374c3f58b?q=80&w=1740&auto=format&fit=crop",{"imageAlt":743,"imageCredit":744,"tags":745},"Teal LED security panel representing digital identity and interconnected authentication systems","Photo by Adi Goldstein on Unsplash",[746,747,748,749,750,751],"security","oauth","microsoft","identity","azure","vulnerabilities",{"title":38,"description":739},"R32sDfrCCqsLszbyJc0yWKnJyb4blSmuIh3nfVaz8QU",[755,757],{"title":34,"path":35,"stem":36,"description":756,"children":-1},"A critical examination of Infrastructure as Code tools in 2025, with Terraform's dominance facing serious competition from Pulumi, CloudFormation, and emerging alternatives.",{"title":42,"path":43,"stem":44,"description":758,"children":-1},"Why Software Development Engineers in Test represent the future of quality assurance, and why enterprises that don't invest in SDET talent are setting themselves up for failure.",1780276238511]