What Is Entra ID Redirect URI Hijacking
An Entra ID redirect URI — Microsoft's docs also call it a reply URL — is the endpoint where the Microsoft identity platform sends a user, together with a token or an authorization code, once they've signed in and consented. Every app registration that uses the OAuth 2.0 authorization code flow, the implicit grant flow, or OpenID Connect must list its exact redirect URIs in advance; Entra ID refuses to redirect anywhere else and returns AADSTS50011 if the value in the sign-in request doesn't match one it has on file.
That matching step is the entire security boundary. Three configuration choices on a single app registration weaken it enough to let an attacker walk away with a live token instead of the legitimate app:
- Wildcard reply URLs (
APP_REPLY_URL_WILDCARD) — a pattern likehttps://*.contoso.cominstead of an exact URL. - HTTP reply URLs (
APP_REPLY_URL_HTTP) — a redirect URI that isn't restricted tohttps. - Implicit grant enabled (
APP_IMPLICIT_GRANT_ENABLED) — the app registration still lets Entra ID hand back an access or ID token directly in the browser, instead of through a back-channel token exchange.
Each of these is High severity on its own in the EtcSec catalogue. Combined on the same app, they turn a single phishing link into a working token-exfiltration path — and they sit alongside other app registration risks EtcSec tracks, like over-privileged app registrations, stale credentials, and dangerous Graph API permissions.
How It Works
Exact matching is the point, wildcards defeat it
Entra ID requires redirect URIs to begin with https, with a narrow carve-out for localhost during local development — http://localhost/myapp is valid, http://contoso.com/callback is not. Wildcards are a separate, optional relaxation of the matching rule, not the scheme rule: an app registration scoped to work-or-school accounts only can, through the application manifest editor, register https://*.contoso.com and have Entra ID treat any matching subdomain as authorized. Microsoft's own redirect URI best practices and limitations guidance is explicit that this should be avoided: per RFC 6749 section 3.1.2, a redirection endpoint must be an absolute URI, and when a wildcard entry matches, Entra strips the query string and fragment from the redirect — which is also where an implicit-flow token would be riding.
RFC 9700, the IETF's OAuth 2.0 Security Best Current Practice, goes further than "avoid": it requires authorization servers to use exact string matching for redirect URIs, with the sole exception of the port number on native-app localhost URIs. The rationale is concrete — naive wildcard implementations have let attackers register something like attacker.example/.somesite.example and pass a substring match, and even correct wildcard handling still leaves subdomain takeover as a live path: any subdomain an attacker can stand up (an abandoned CNAME, a forgotten dev host) becomes a valid redirect target the moment it matches the pattern.
The implicit grant hands the token to whoever holds the URL
The authorization code flow keeps the token exchange on a back channel: the browser only ever sees a short-lived code, which the app then trades for a token server-to-server. The implicit grant skips that step — Entra ID returns the access token or ID token directly in the /authorize response, appended to the redirect URI's fragment. That behavior is controlled per app registration by two Microsoft Graph properties on the application's web resource: implicitGrantSettings.enableIdTokenIssuance and implicitGrantSettings.enableAccessTokenIssuance. If either is true, the app can request a token or id_token response type and get a token back with no server-side exchange at all.
RFC 9700 section 2.1.2 is direct about the consequence: clients "SHOULD NOT use the implicit grant … unless access token injection in the authorization response is prevented and the aforementioned token leakage vectors are mitigated." The leakage vectors it lists are unglamorous but real — a token sitting in a URL fragment can end up in browser history, get reattached by the user agent across redirects, or leak through a Referer header to a third-party site the token-bearing page happens to link to. On top of that, an implicit-flow token has no sender-constraining: nothing binds it to the client it was issued for, so a copy is as good as the original. Microsoft's own implicit grant flow documentation has arrived at the same conclusion from an unrelated angle — browsers dropping third-party cookies broke the flow's silent-renewal step — and now tells developers outright not to use it, pointing at RFC 9700 section 2.1.2 and recommending migration to the authorization code flow.
The Attack Chain
Step 1 — Finding or hijacking a redirect target that matches
If the target app registration carries APP_REPLY_URL_WILDCARD — say https://*.contoso.com/auth — the attacker doesn't need to compromise the primary domain. Any subdomain that resolves, including one from an abandoned dev environment or a dangling DNS record left by a decommissioned service, matches the pattern and becomes a legitimate-looking redirect target for Entra ID.
Step 2 — Requesting a token instead of a code
With APP_IMPLICIT_GRANT_ENABLED set on the app, the attacker builds a sign-in URL against the real client_id with response_type=token (or id_token token) and redirect_uri set to their matching endpoint, then delivers it as a phishing link. The victim authenticates against the genuine Entra ID sign-in page — there's no fake login form to spot — and Entra ID redirects the browser back to the attacker's endpoint with the token attached.
Step 3 — Catching the token in the clear
Where APP_REPLY_URL_HTTP is also present, or where the endpoint the attacker controls simply logs what it receives, the token riding the URL can be captured directly, logged by a proxy, or exposed through the leakage vectors RFC 9700 documents — browser history, Referer headers on outbound links from the landing page, or reattachment during a further redirect.
Step 4 — Replaying the token with no client secret required
Because the implicit grant issues a bearer token with no sender-constraining, the attacker uses it directly against Microsoft Graph or whatever resource it was scoped to, as the victim, for as long as it remains valid. There's no refresh token in this flow, so the window is bounded by the access token's lifetime — but nothing else stands between capture and reuse. This is a different mechanism from OAuth consent phishing, which tricks a user into authorizing a malicious app rather than intercepting a token meant for a legitimate one — but both end with an attacker holding a live credential to your tenant.
Detection
Redirect URI configuration and implicit-grant settings live on the application object, not in sign-in activity logs, so this is an inventory check against Microsoft Graph rather than a SIEM query:
| Indicator | Where to check | What it means |
|---|---|---|
A web.redirectUris entry containing * | GET /applications/{id} — web.redirectUris | Wildcard reply URL — APP_REPLY_URL_WILDCARD |
A web.redirectUris entry starting with http:// and not localhost | Same web.redirectUris array | Plaintext reply URL — APP_REPLY_URL_HTTP |
web.implicitGrantSettings.enableIdTokenIssuance or enableAccessTokenIssuance set to true | GET /applications/{id} — web.implicitGrantSettings | Implicit or hybrid flow still allowed — APP_IMPLICIT_GRANT_ENABLED |
In the Microsoft Entra admin center, the same information is visible per app under App registrations → [app] → Authentication: the Web platform's redirect URI list, and the Implicit grant and hybrid flows checkboxes for ID tokens and access tokens.
Remediation
- Replace wildcard redirect URIs with exact, absolute URIs. List every real callback endpoint individually instead of a pattern. If the number of subdomains makes that unmanageable, Microsoft documents a state-parameter alternative: a single shared, CSRF-protected redirect URI plus an app-specific
statevalue, instead of a wildcard match. - Remove any non-localhost
http://redirect URI. Entra ID already rejects newhttps-less entries outsidelocalhost, but legacy app registrations can carry one from before that restriction was enforced; checkweb.redirectUrisdirectly rather than assuming the admin center would have blocked it at creation time. - Disable the implicit grant. Set both
implicitGrantSettings.enableIdTokenIssuanceandenableAccessTokenIssuancetofalse(or uncheck the two boxes in the portal) unless a legacy integration explicitly depends on them. - Migrate to the authorization code flow with PKCE. RFC 9700 section 2.1.1 requires PKCE for public clients and recommends it for confidential clients; Microsoft's MSAL libraries implement the code exchange and PKCE challenge for you, so this is usually a library swap rather than a protocol-implementation project.
- Re-check the platform after migration. A single-page app that still needs a browser-only flow should move to the authorization code flow with PKCE on the SPA platform, not stay on Web with the implicit grant enabled.
How EtcSec Detects This
EtcSec's Azure audit reads every app registration's web.redirectUris and web.implicitGrantSettings through Microsoft Graph, and raises APP_REPLY_URL_WILDCARD for any wildcard entry, APP_REPLY_URL_HTTP for any non-localhost http:// entry, and APP_IMPLICIT_GRANT_ENABLED when either implicit-grant issuance flag is true — all three scoped to the Web platform, as noted above. This cluster is one part of a broader review; see our practical guide to auditing Microsoft Entra ID security for the full checklist beyond app registrations.
Explore the identity security pages that support this topic
