A redirect URI mismatch means the redirect_uri Angular sends to /connect/authorize is not character-exact on the OIDC client registration — scheme, host, path, trailing slash. A login loop often means the URI was accepted but tokens never landed in the SPA.
Angular → /connect/authorize?redirect_uri=...
│
valid URI? ──no──► error / retry loop
│
yes
▼
/auth-callback?code=...
│
token exchange OK? ──no──► authorize again (loop)
New to this → stay here. Merging a PR → two failures table. On-call / interview → mismatch rules · debug checklist · if an interviewer asks.
People search IdentityServer when Angular bounces between /connect/authorize and the app origin and nobody is logged in. Logs say redirect_uri is not valid, or they say nothing. Swagger’s password flow still works.
Whether you should run an identity server at all is IdentityServer vs ASP.NET Identity. Leaving IS4 is the OpenIddict checklist. This URL is client redirect URIs and the login loop — not another SSO essay.
Two failures that look the same in the SPA
| What you see | What actually failed |
|---|---|
| Authorize → error page / “invalid request” | redirect_uri not on the client (character-exact) |
| Authorize → app → authorize forever | URI was valid; SPA dropped code, state, or tokens |
Do not debug Angular interceptors until you know which row you are in.
Mismatch is character-exact
I have failed reviews on trailing slashes, http vs https, www vs apex, and a silent-renew iframe on a path that was never registered.
client.RedirectUris =
{
"http://localhost:4200/auth-callback",
"http://localhost:4200/silent-renew.html",
"https://app.example.com/auth-callback",
"https://app.example.com/silent-renew.html",
};
client.PostLogoutRedirectUris =
{
"http://localhost:4200/",
"https://app.example.com/",
};
redirect_uri (login) and post_logout_redirect_uri (logout) are different lists. Registering the callback does not register /. Slot hosts (*.azurewebsites.net) are extra rows, not optional.
Angular (angular-auth-oidc-client or similar) must send the same strings:
redirectUrl: `${window.location.origin}/auth-callback`,
postLogoutRedirectUri: window.location.origin,
silentRenewUrl: `${window.location.origin}/silent-renew.html`,
authority: 'https://id.example.com', // must match discovery issuer
Log the requested redirect_uri next to the client’s allowed list. If you cannot see that pair, you are guessing.
The login loop (URI was accepted)
The host issued a code. The SPA never “lands.” Causes I see that are not in the comparison article:
authority≠ discoveryissuer— trailing slash on one side/connect/tokenfails — PKCE verifier lost, CORS on the token endpoint, or confidential client without a secret the SPA should not have- CDN or PathBase stripped
?code=— gateway maps/idto the host but Angular returns to/auth-callbackwithout the query - Two tabs — state/nonce consumed once; the second tab retries authorize
- Interceptor treats missing access token as logged-out and sends the user back to authorize
The loop looks like “IdentityServer is broken.” Tokens never made it into memory. Interceptor plumbing is Angular JWT interceptors, not this page.
PathBase and the public origin
Behind a reverse proxy I set the public origin once:
app.Use((context, next) =>
{
context.Request.Scheme = "https";
return next();
});
// plus UseForwardedHeaders — see middleware order
If IdentityServer issues http://internal:5001 in the authorize redirect, Angular will not match redirectUrl. Pipeline order for forwarded headers is middleware order.
What I check (this bug only)
- IdentityServer log: was
redirect_urivalid? If no, stop. Fix the client row. - Network:
/connect/authorize302 to the SPA withcode=still on the URL /connect/token200 withaccess_token- Discovery
issuer=== Angularauthority GET /api/mesendsAuthorization: Bearer
If step 2 fails, registration or HTTPS/PathBase. If step 3 fails, PKCE/CORS/client type. If step 5 fails, interceptor — different article.
Checklist
- Login callback, silent renew, and logout URIs are three concerns, all registered
- No trailing-slash / www / scheme drift vs Angular
- Staging slot URL is on the client if you test there
- Discovery issuer === Angular authority
- Token request succeeds (not only authorize)
- Query string survives the reverse proxy
If an interviewer asks
Redirect URI mismatch vs login loop; why trailing slashes matter; what to check before blaming Angular interceptors.
Strong answer: Mismatch = redirect_uri not on the client list — fix registration first. Loop = authorize succeeded but code/token never stuck — check PKCE, CORS on /connect/token, PathBase, and interceptor 401 handling. Log requested URI next to allowed list before rewriting SPA code.