Microsoft Identity Platform: OAuth 2.0, MSAL, and App Registration Guide
Why This Is Happening
Here's the scenario I see constantly: a developer spends an afternoon wiring up sign-in for their new app, hits "Run," and gets back something like AADSTS50011: The redirect URI specified in the request does not match or a blank screen with no error at all. I know how infuriating that is , especially when the official error message gives you almost nothing useful to go on.
The Microsoft identity platform is a cloud identity service that lets you build applications your users can sign in to using their Microsoft accounts , work or school accounts provisioned through Microsoft Entra ID, personal Microsoft accounts (Skype, Xbox, Outlook.com), or even social and local accounts via Azure AD B2C or Microsoft Entra External ID. That sounds simple enough. In practice, getting all the pieces to talk to each other correctly involves a handful of systems that each have their own quirks.
The platform itself is built on OAuth 2.0 and OpenID Connect (OIDC) standards, which is genuinely good news, those are well-understood protocols. But the implementation details, specifically around app registration in the Microsoft Entra admin center, redirect URI configuration, token scope declarations, and the Microsoft Authentication Library (MSAL) setup in your code, are where developers lose hours.
The most common root causes I've diagnosed break down like this:
- Redirect URI mismatch, the single biggest cause of
AADSTS50011. The URI in your code doesn't exactly match what's registered in Entra, right down to the trailing slash. - Wrong account type selected during registration, choosing "single tenant" when you need multi-tenant app sign-in, or vice versa, causes silent failures that are very hard to trace.
- Missing or wrong API permissions, your app requests a scope like
User.Readbut the permission was never granted admin consent in the tenant. - Incorrect MSAL configuration, wrong client ID, wrong authority URL, or a mismatched tenant ID all produce authentication errors that look identical on the surface.
- Token cache issues, stale tokens in MSAL's token cache can cause silent authentication failures that look like the user is being repeatedly prompted to sign in.
- Using deprecated ADAL instead of MSAL, if you're maintaining an older app, ADAL (Azure AD Authentication Library) is retired; calls to ADAL endpoints silently fail in some tenants.
The reason Microsoft's error messages feel so opaque is that they're designed with tenant security in mind, they deliberately avoid revealing too much about the tenant configuration to unauthenticated callers. That's the right call from a security standpoint, but it makes debugging painful for developers.
Whether you're building a single-page app (SPA), a traditional server-side web app, a desktop or mobile app, a background daemon, or a protected web API, the Microsoft identity platform setup process follows the same core flow. This guide walks through every part of that flow with the exact settings, error codes, and MSAL configurations that actually fix the problem. Browse all Microsoft fix guides →
The Quick Fix, Try This First
If you're hitting an authentication error right now and need a fast answer, the single fix that resolves the majority of Microsoft identity platform issues is verifying your redirect URI registration. This is so often the culprit that I always check it first before anything else.
Here's what you do:
- Open the Microsoft Entra admin center (formerly the Azure portal Azure AD blade).
- Go to Applications > App registrations and find your app.
- Click your app, then click Authentication in the left sidebar.
- Under Platform configurations, find the platform type your app uses (Single-page application, Web, Mobile and desktop applications).
- Check the Redirect URIs listed there. Copy one exactly, character for character.
- Compare it to the
redirectUrivalue in your MSAL configuration object in code.
They must be byte-for-byte identical. https://localhost:3000 and https://localhost:3000/ are different URIs. http:// and https:// are different. Uppercase letters matter. If there's any difference at all, fix either the portal entry or your code to make them match, redeploy, and test again.
If your redirect URI is correct and you're still seeing errors, the second fastest check is your client ID. In the Entra admin center, go to App registrations > your app > Overview. The "Application (client) ID" shown there is the GUID you must pass as clientId in MSAL. I've seen teams accidentally use the "Object ID" instead, they look similar but they are completely different values and the wrong one will always produce an authentication failure.
For MSAL.js (JavaScript/TypeScript), your config should look like this:
const msalConfig = {
auth: {
clientId: "your-application-client-id-guid",
authority: "https://login.microsoftonline.com/your-tenant-id",
redirectUri: "https://your-exact-registered-uri.com/callback"
}
};
If neither of those fixes it, keep reading, there are several other configuration layers that can be the source of the problem.
Everything in the Microsoft identity platform starts with app registration. If you skip this or do it wrong, nothing downstream will work, not MSAL, not OAuth 2.0 flows, not Microsoft Graph API calls. The registration is what gives your app an identity in the directory.
Go to the Microsoft Entra admin center and sign in with an account that has at least the Application Developer role. Navigate to Applications > App registrations > New registration.
You'll see three fields that trip up most developers:
Name, this is just a display name. Make it descriptive. Users will see it on the consent screen.
Supported account types, this is the one that causes the most confusion. Your four choices are:
- Accounts in this organizational directory only (Single tenant), only users in your own Entra tenant can sign in. Right for internal line-of-business apps.
- Accounts in any organizational directory (Multitenant), users from any Entra tenant (any company) can sign in. Right for SaaS products.
- Accounts in any organizational directory and personal Microsoft accounts, the broadest option. Supports both work/school accounts and personal accounts (Xbox, Outlook.com, Skype).
- Personal Microsoft accounts only, consumer apps only.
If you choose single-tenant now and later need multi-tenant, you can change it, but it requires re-testing the entire auth flow. Choose based on who your actual users are, not what seems easiest today.
Redirect URI, you can add this here or later in the Authentication blade. Set it now if you know it. For local development, http://localhost:3000 is accepted for SPA and mobile app platform types.
After you click Register, copy the Application (client) ID and Directory (tenant) ID from the Overview page immediately. Put them somewhere safe, you'll need both in your MSAL config.
If registration succeeds, you'll land on the app's Overview page. That's your confirmation it worked.
This is the step where most OAuth 2.0 app authentication errors originate. After registering your app, click Authentication in the left sidebar. If you didn't add a redirect URI during registration, this page will be mostly empty. You need to add a platform configuration that matches your app type.
Click Add a platform and choose the correct type for your app:
- Single-page application, for React, Angular, Vue, and other JavaScript SPAs. This enables the Authorization Code flow with PKCE, which is the correct modern flow for SPAs. Do NOT use the "Web" platform for a SPA, that's a common mistake that enables implicit grant, which Microsoft now discourages.
- Web, for traditional server-side apps (ASP.NET, Node/Express, Django, etc.) where the redirect URI is a server-side endpoint.
- Mobile and desktop applications, for native apps. This includes pre-registered URIs like
msal{clientId}://authfor iOS/macOS and custom scheme URIs for Android. - Public client / native, for desktop apps or daemons that don't have a web redirect.
Under each platform, enter your redirect URI. For development, you typically want something like http://localhost:3000 or http://localhost:3000/auth/callback. For production, use your actual HTTPS domain.
One thing that surprises developers: you can register multiple redirect URIs per platform. Add both your dev and production URIs so you don't have to change the registration between environments.
Also on this page: for most modern apps, make sure both Access tokens and ID tokens are unchecked under "Implicit grant and hybrid flows." The Authorization Code flow with PKCE (which MSAL handles automatically) is more secure and doesn't need implicit grant enabled.
Save changes, then verify the saved values match your MSAL config. When this step is correct, the AADSTS50011 redirect URI mismatch error disappears.
The Microsoft Authentication Library (MSAL) is Microsoft's open-source, officially supported library for handling OAuth 2.0 and OpenID Connect authentication. Microsoft recommends MSAL for any app that uses the Microsoft identity platform, and for good reason. It handles token acquisition, renewal, caching, and the complex parts of the authorization code flow so you don't have to.
MSAL is available for .NET, Android, Angular, iOS & macOS, Java, JavaScript, Node.js, Python, and React. Install the one that matches your stack. For a React app:
npm install @azure/msal-browser @azure/msal-react
Then create your MSAL instance. The authority URL format depends on your account type selection from Step 1:
// Single-tenant app:
authority: "https://login.microsoftonline.com/{your-tenant-id}"
// Multi-tenant app (any org):
authority: "https://login.microsoftonline.com/organizations"
// Multi-tenant + personal accounts:
authority: "https://login.microsoftonline.com/common"
// Personal accounts only:
authority: "https://login.microsoftonline.com/consumers"
Using the wrong authority is a silent killer. If you registered a single-tenant app but set authority to /common, users from other tenants will appear to authenticate but then fail when your API checks their tenant claim.
For a React SPA, wrap your app with MsalProvider:
import { PublicClientApplication } from "@azure/msal-browser";
import { MsalProvider } from "@azure/msal-react";
const msalInstance = new PublicClientApplication(msalConfig);
function App() {
return (
<MsalProvider instance={msalInstance}>
<YourAppComponents />
</MsalProvider>
);
}
When this is wired up correctly, calling loginRedirect() or loginPopup() will open the Microsoft sign-in page. A successful sign-in redirects back to your registered URI with a code in the query string, which MSAL exchanges silently for tokens. You never handle the raw OAuth 2.0 code yourself, MSAL does it.
After a successful sign-in, you should see an account object returned. That's your confirmation MSAL is configured correctly.
Just because a user signs in doesn't mean your app can do anything. The Microsoft identity platform uses scopes to control what your app can access. You need to declare the permissions your app needs, and in some cases, a tenant administrator must explicitly grant those permissions.
In the Entra admin center, go to your app registration and click API permissions. Click Add a permission. You'll see two main categories:
- Microsoft APIs, this includes Microsoft Graph (for accessing user data, calendar, mail, groups, etc.) and other Microsoft services.
- My APIs, for calling your own registered web APIs (more on that below).
For most apps, you'll start with Microsoft Graph. The User.Read scope (under Delegated permissions) lets your app read the signed-in user's profile. It's the minimum scope for most sign-in scenarios and it's one of the few that doesn't require admin consent.
There are two types of permissions:
- Delegated permissions, the app acts on behalf of a signed-in user. The user (or an admin) consents, and the app gets access scoped to what that user can access.
- Application permissions, the app acts as itself, with no signed-in user. This is for daemons, background services, and scripts. These always require admin consent, a user can never grant them on their own.
If your app requests admin-only permissions (like User.ReadAll or Directory.Read.All) and the user isn't an admin, they'll hit a consent screen they can't get past. The error looks like AADSTS65001: The user or administrator has not consented to use the application.
The fix: either request only delegated permissions that users can self-consent to, or have a tenant admin go to API permissions > Grant admin consent for [tenant] to pre-authorize the app for all users.
In your MSAL config, request scopes at sign-in time:
const loginRequest = {
scopes: ["User.Read", "Mail.Read"]
};
msalInstance.loginRedirect(loginRequest);
When this step is correct, users see an accurate consent screen listing exactly what the app will access, and they can sign in without hitting consent errors.
Many applications aren't just signing users in, they're calling a backend API or accessing Microsoft Graph to read organizational data. The Microsoft identity platform covers both scenarios, but the setup differs from sign-in.
Protecting your own web API: If you're building a RESTful API that only authenticated apps should call, you need to register it in Entra separately from your client app. In the Entra admin center, register a new app for your API, then go to Expose an API. Click Add a scope and define what operations callers can request (e.g., api://your-api-client-id/Data.Read). Then in your client app's registration, add this scope under API permissions > My APIs.
In your API code, validate the incoming bearer token on every request. The token is a JWT, you must verify:
- The
iss(issuer) claim matches your expected tenant - The
aud(audience) claim matches your API's client ID - The token hasn't expired (
expclaim) - The signature is valid (use Microsoft's JWKS endpoint, MSAL server-side libraries handle this)
# Example: validate token audience in Python (msal + Flask)
# Decoded token claims should include:
# claims["aud"] == "your-api-client-id"
# claims["iss"] == "https://sts.windows.net/your-tenant-id/"
Calling Microsoft Graph: Microsoft Graph gives you programmatic access to organizational data, users, groups, mail, calendar, files, and more, stored in Microsoft Entra ID. After acquiring a token with the right scopes, call the Graph endpoint:
// Get access token silently (MSAL handles cache + renewal)
const tokenResponse = await msalInstance.acquireTokenSilent({
scopes: ["User.Read"],
account: msalInstance.getAllAccounts()[0]
});
// Call Microsoft Graph
const response = await fetch("https://graph.microsoft.com/v1.0/me", {
headers: {
Authorization: `Bearer ${tokenResponse.accessToken}`
}
});
const userData = await response.json();
If acquireTokenSilent throws an InteractionRequiredAuthError, it means the token cache is empty or the token can't be refreshed silently, you need to call acquireTokenRedirect or acquireTokenPopup to get a fresh token interactively. MSAL will tell you exactly when this is needed.
When this is working, your API calls return 200 responses with real user or org data. A 401 Unauthorized from Graph means your token is wrong, check scopes and audience.
Advanced Troubleshooting
If the steps above didn't resolve your issue, the problem is likely deeper in the configuration. Here's where I dig when the basics are ruled out.
Decoding JWT Tokens to Diagnose Claim Errors
The access token and ID token returned by the Microsoft identity platform are JSON Web Tokens (JWTs). When something is wrong with permissions, tenant, or audience, the token itself usually contains the evidence. Go to jwt.ms (Microsoft's own tool) and paste your token. You'll see every claim decoded in a readable format. Look at:
tid, the tenant ID. Should match your registered app's tenant.aud, the audience. For Microsoft Graph tokens, this should behttps://graph.microsoft.com. For your own API, it should be your API's client ID.scp, scopes granted. If the scope you requested isn't listed here, the permission wasn't granted or you forgot to request it.roles, application roles, if you use role-based access control.
Conditional Access Policy Blocks
In enterprise environments, tenant administrators often configure Conditional Access policies that require multi-factor authentication (MFA), compliant devices, or specific network locations. If a user's sign-in is blocked by a policy your app doesn't handle, MSAL returns an InteractionRequiredAuthError with a claims property attached. You need to pass those claims back in the next interactive authentication request:
try {
await msalInstance.acquireTokenSilent(tokenRequest);
} catch (error) {
if (error instanceof InteractionRequiredAuthError) {
await msalInstance.acquireTokenRedirect({
...tokenRequest,
claims: error.claims // Pass Conditional Access claims back
});
}
}
Multi-Tenant App Consent Issues
For multi-tenant apps, every tenant that uses your app needs to consent to it, either through user consent or admin consent. If a user from a new tenant hits AADSTS65001, their tenant admin hasn't consented yet. You can build an admin consent flow using the endpoint:
https://login.microsoftonline.com/{tenant}/adminconsent
?client_id={your-client-id}
&redirect_uri={your-redirect-uri}
Direct tenant admins to this URL to pre-authorize your app in their organization.
Event Viewer for Azure AD Join & Hybrid Scenarios
On Windows devices that are Azure AD Joined or Hybrid Joined, Windows handles some authentication flows at the OS level via the Web Account Manager (WAM). If silent SSO is failing on Windows, open Event Viewer > Applications and Services Logs > Microsoft > Windows > AAD. Event IDs in the 1000–1100 range indicate WAM authentication failures with far more detail than MSAL's error messages alone.
App Roles and Role-Based Access Control
If your app uses roles to restrict access (e.g., only users in an "Admin" role can view certain pages), those roles must be defined in the app manifest. Go to App registrations > your app > App roles and create the roles there. Then assign users or groups to those roles in Enterprise applications > your app > Users and groups. The assigned roles appear in the JWT's roles claim.
AADSTS700016 (application not found in directory), which sometimes indicates a tenant-side configuration issue you can't fix yourself. Collect your correlation ID from the failed request (it's in the MSAL error object and in the AAD sign-in logs) before contacting Microsoft Support, they'll need it to trace the exact request through Microsoft's systems.
Prevention & Best Practices
Once you've got the Microsoft identity platform working, keeping it working requires a bit of ongoing hygiene. These are the practices I recommend to every team building on this platform.
Use MSAL, always. The older Azure AD Authentication Library (ADAL) is retired. If you're maintaining a legacy application that still uses ADAL, migration to MSAL isn't optional, it's necessary. MSAL handles the latest security requirements, supports modern authentication flows including Authorization Code + PKCE, and receives active security patches. ADAL doesn't.
Never store client secrets in code. If your app is a confidential client (a server-side web app or daemon), you'll have a client secret or certificate. Never commit these to source control. Use environment variables in development and Azure Key Vault or your deployment platform's secrets management in production. A leaked client secret is a full app impersonation risk.
Request only the scopes you actually need. This isn't just a security best practice, it directly affects your app's user experience. The consent screen shows users every permission your app requests. Requesting Mail.ReadWrite when you only need User.Read will cause users to distrust your app or refuse consent entirely. Scope down to the minimum required.
Handle token expiry gracefully. Access tokens from the Microsoft identity platform typically expire in one hour. MSAL's acquireTokenSilent handles refresh automatically as long as the refresh token is valid (refresh tokens last up to 90 days for active sessions). But your code must handle the InteractionRequiredAuthError case and redirect to an interactive login when silent renewal fails. Don't assume a token is valid just because you acquired it earlier in the session.
Use separate app registrations for dev, staging, and production. Sharing one registration across environments is tempting but creates risks, a developer adding a test redirect URI to the production registration is a real thing that happens. Separate registrations give you clean separation of credentials, redirect URIs, and permissions per environment.
- Turn on the Microsoft Entra sign-in logs (Monitoring & health > Sign-in logs), every failed authentication appears here with the full correlation ID and failure reason, far more detail than any client-side error message.
- Enable token version 2.0 in your app manifest (
"accessTokenAcceptedVersion": 2), the v1.0 token format is legacy and the issuer format differs, which causes validation failures if you're not expecting it. - Add your app's service principal to the Enterprise applications blade and review the Users and Groups tab quarterly, permission drift is real, especially in long-running SaaS apps.
- For SPAs, test your auth flow in a private/incognito window regularly, cached credentials in normal sessions can mask token acquisition failures that real new users will hit.
Frequently Asked Questions
What is the Microsoft identity platform and how is it different from Azure AD?
The Microsoft identity platform is the evolution of Azure Active Directory (now called Microsoft Entra ID) focused specifically on application authentication and authorization. It's built on open standards, OAuth 2.0 and OpenID Connect, and it's the system developers interact with when they want to add sign-in, protect APIs, or call Microsoft services like Microsoft Graph from their apps. Azure AD (Entra ID) is the underlying directory service that stores users and groups; the identity platform is the developer-facing layer on top of it. When you register an app, configure redirect URIs, declare API permissions, or use MSAL, you're working with the Microsoft identity platform.
I keep getting AADSTS50011 even though my redirect URI looks correct, what am I missing?
The most common hidden cause is a trailing slash difference, https://myapp.com/auth and https://myapp.com/auth/ are treated as different URIs. Also check that the platform type in the Entra admin center matches your app type, a SPA must be registered under the "Single-page application" platform, not "Web." A third gotcha: if your app does a redirect that adds query parameters to the URI before hitting the redirect endpoint, those parameters make it not match. The redirect URI must match exactly what's registered, with no extra query strings.
What's the difference between delegated permissions and application permissions in app registration?
Delegated permissions are used when there's a signed-in user, the app acts on their behalf and can only do what that user is allowed to do. The user (or an admin) grants consent on first sign-in. Application permissions are for apps that run without a user present, like scheduled jobs or server-to-server integrations. Because no user is there to give consent, application permissions always require an admin to grant them ahead of time. If you're building a background service that reads all users in a tenant (using User.ReadAll), that's an application permission, no regular user can approve it.
How do I add sign-in to a React app using MSAL and the Microsoft identity platform?
Install @azure/msal-browser and @azure/msal-react, create a PublicClientApplication with your client ID and authority URL, and wrap your app with MsalProvider. Register your app in the Microsoft Entra admin center under the "Single-page application" platform with your React app's redirect URI, typically http://localhost:3000 for development. Use the useMsal hook or the AuthenticatedTemplate/UnauthenticatedTemplate components from @azure/msal-react to show different content based on sign-in state. Call instance.loginRedirect() or instance.loginPopup() to trigger the sign-in flow.
Do I need to register my app if I'm just calling Microsoft Graph?
Yes, every application that calls Microsoft Graph, or any Microsoft API, must be registered in the Microsoft Entra admin center first. App registration is what gives your app a client ID that Microsoft's authentication servers recognize. Without registration, there's no way to obtain a valid access token that Graph will accept. The registration also defines which Microsoft Graph permissions your app has been granted, which directly controls what data you can read or write through the API.
What's the right OAuth 2.0 flow to use for each app type?
The Microsoft identity platform recommends specific flows per app type. Single-page applications should use the Authorization Code flow with PKCE (MSAL handles this automatically when you register under the SPA platform). Traditional server-side web apps also use Authorization Code flow, but with a client secret instead of PKCE. Desktop and mobile apps use Authorization Code with PKCE as well. Background services and daemons with no signed-in user should use the Client Credentials flow, where the app authenticates with its own credentials rather than on behalf of a user. Never use the Implicit flow for new apps, it's been deprecated in favor of Authorization Code + PKCE for browser-based applications.