AADSTS50011: Entra redirect URIs match exactly

A missing trailing slash in an app registration breaks sign-in with no other symptom. Why it happens, and how to find and fix the mismatch.

  • Entra ID
  • Azure
  • OAuth
  • Debugging

A site loads perfectly. Certificate is valid, pages render, no console errors. Click Sign in and Microsoft throws you back with:

AADSTS50011: The redirect URI specified in the request does not match
the redirect URIs configured for the application.

The app registration has an entry that looks right. It is not right. Somewhere there is a character difference, and nine times out of ten it is a trailing slash.

Entra compares redirect URIs as exact strings

This is the whole thing, and it is worth stating plainly because it is not how most URL handling works. Browsers, web servers and DNS are all forgiving about trailing slashes. https://example.com and https://example.com/ reach the same page everywhere else in the stack.

Entra ID does not treat them as the same value. The comparison is a literal string match. No normalisation, no canonicalisation, no ignoring case in the path. If your application sends https://app.example.com/ and the registration contains https://app.example.com, that is a mismatch and sign-in fails.

The reason is security rather than laziness. Redirect URIs are where authorization codes get delivered. Any fuzziness in matching is an opening for redirecting tokens somewhere unintended, so the specification and Microsoft’s implementation both err hard toward strictness.

Work out what your app actually sends

Do not read the registration and assume. Read the code, because the value is usually computed rather than written down.

Three patterns cover most single-page applications:

// 1 — origin plus a slash. ALWAYS has a trailing slash.
var REDIRECT = window.location.origin + '/';
//    https://app.example.com/

// 2 — current URL, stripped of query and fragment.
//     Value differs per page. Every page needs its own entry.
var REDIRECT = location.href.split('?')[0].split('#')[0];
//    https://app.example.com/reports.html

// 3 — hardcoded. Breaks the moment you add a custom domain.
var REDIRECT = 'https://old-host.example.net/';

Pattern two is the one that catches people out. If three pages each kick off their own sign-in, and each derives its redirect from location.href, then you need three registered URIs, not one. Registering only the root leaves the other two failing while the home page works — which reads like an intermittent bug rather than a configuration problem.

The fastest way to confirm without guessing is to attempt a sign-in and read the redirect_uri parameter straight off the authorize URL in the address bar, or grab it from the network tab. That is the literal string Entra is comparing against.

Check the registration from the CLI

Faster than clicking through the portal, and it shows you the exact strings including whitespace:

az ad app show --id <application-client-id> \
  --query "{spa:spa.redirectUris, web:web.redirectUris}" -o json

Note that SPA and Web are separate collections. A browser-based application using PKCE belongs in SPA. Putting the URI under Web instead produces a different but equally confusing failure, because Web expects a client secret and rejects the flow your JavaScript is running.

Fix it without clobbering what is there

The temptation is to overwrite the list. Do not — you will silently break whatever else was using the old entries, which typically includes a staging environment somebody still relies on.

Read the current values, append, then patch:

APP_ID=<application-client-id>
OBJ_ID=$(az ad app show --id $APP_ID --query id -o tsv)

# inspect first
az ad app show --id $APP_ID --query "spa.redirectUris" -o json

# then PATCH the full list — existing entries plus the new ones
az rest --method PATCH \
  --uri "https://graph.microsoft.com/v1.0/applications/$OBJ_ID" \
  --headers 'Content-Type=application/json' \
  --body '{"spa":{"redirectUris":[
    "https://app.example.com/",
    "https://app.example.com/reports.html",
    "https://existing-host.example.net/"
  ]}}'

Changes take effect within a minute or so. There is no cache to clear on your side.

Verify without signing in

You can confirm the fix before bothering a real user. Request the authorize endpoint directly with the exact redirect URI and see what comes back:

https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
  ?client_id=<application-client-id>
  &response_type=code
  &redirect_uri=https%3A%2F%2Fapp.example.com%2F
  &response_mode=query
  &scope=openid
  &code_challenge=<any-valid-challenge>
  &code_challenge_method=S256

If the URI is wrong you get an error page containing AADSTS50011. If it is right you get the ordinary sign-in page. No credentials required either way — the redirect URI is validated before authentication.

When this bites

Almost always after a hostname change. A custom domain gets attached, a Static Web App is recreated with a new generated name, an app moves from a test host to production. The application keeps computing its redirect from wherever it is served, and the registration keeps listing wherever it used to live.

Two habits avoid most of it. When you attach a custom domain to anything using Entra sign-in, update the redirect URIs in the same change — not as a follow-up ticket. And when you register a URI, copy it from the redirect_uri parameter your app actually emits rather than typing it out. Typing is where the slash goes missing.


Ingot Solutions builds custom software and Azure integrations for Microsoft-first organizations. If something in your tenant is behaving like this and you would rather not spend the afternoon on it, get in touch.

Next step

Tell me what is not working.

A short call, no pitch deck. You describe the problem; you get a straight recommendation on how to approach it — the architecture, the sequence, and what to build first. If it is a fit, that becomes a scoped plan with a fixed price and a date.