cyber-security identity oauth claude-curated

The redirect_uri parameter in OAuth 2.0 tells the authorisation server where to send the user back after authentication, carrying an authorisation code or token. It is one of the most security-critical parameters in the protocol, and at scale it becomes one of the most operationally painful. See also callback URL and redirect URI.

Why exact match

The OAuth 2.0 spec (RFC 6749) and the security best-current-practice document require authorisation servers to match redirect_uri against a pre-registered allowlist using exact string comparison. The reason is authorisation code injection — if the auth server allowed loose matching, an attacker could craft a request that returns the code to a domain they control, then exchange it for tokens. See Threat Modelling and OWASP for related risk frameworks.

Exact matching means:

  • No path prefixes (https://app.example.com/ is not the same as https://app.example.com/callback)
  • No wildcards in domain (*.example.com is not allowed by mainstream IdPs). See Apex Domain Limitations.
  • No query string flexibility (a registered URI with a query parameter only matches when the same parameter is present)
  • Trailing slash sensitivity (some IdPs treat /cb and /cb/ as different)

Multi-tenant pain points

For SaaS apps where each customer runs on their own custom domain, redirect URI management explodes:

  • Every customer domain needs its own registered URI per IdP
  • Every environment (dev, staging, prod) multiplies that count
  • Every IdP integration (Google, Microsoft Entra, Okta, generic SAML/OIDC) is a separate registration
  • The URL list scales as tenants × environments × IdPs, easily hundreds or thousands of entries

IdPs vary on caps: Google historically allowed around 100 redirect URIs per OAuth client; Microsoft Entra has its own tier-dependent limits; Okta limits per app. Hitting the cap forces architectural changes, not just config edits. Other IdPs to consider include Auth0, Keycloak, Ping Identity, and OneLogin.

Patterns to manage scale

Pattern A: Single redirect URI on platform domain

Register one redirect URI per IdP, pointing to a platform-owned domain. After the IdP returns the code, the platform exchanges it server-side, then redirects the user to their tenant-specific domain with a session cookie.

  • Pros: minimal IdP config, easy to reason about, scales to unlimited tenants.
  • Cons: extra redirect hop. The user briefly sees the platform domain in the URL bar. Cross-domain cookie handling needs care.

Pattern B: Per-tenant redirect URI registered in IdP

Register a redirect URI for every tenant domain directly with the IdP. Auth completes on the tenant’s own domain.

  • Pros: clean URL — the user never leaves the tenant domain.
  • Cons: the IdP config grows linearly with tenants. Adding a customer becomes an ops task, not a self-serve flow. Hits IdP caps.

Pattern C: Programmatic registration via IdP admin API

Use the IdP’s admin API to register and deregister redirect URIs as tenants come and go.

  • Pros: combines the clean URLs of Pattern B with automation.
  • Cons: requires admin API access (sometimes a paid tier), error handling for partial failures, drift detection between IdP state and platform state. Some IdPs rate-limit admin API operations aggressively.

In practice, mature multi-tenant SaaS often runs Pattern A as the default and offers Pattern B/C only to enterprise customers who require it.

Public clients vs confidential clients

OAuth distinguishes:

  • Confidential clients — server-side apps that can store a client secret safely. Use the authorisation code flow with client secret. See Secrets Manager for secure storage.
  • Public clients — mobile apps, single-page apps, native desktop apps. Cannot keep a secret because the binary is distributed. Use the authorisation code flow with PKCE (Proof Key for Code Exchange, RFC 7636).

PKCE adds a code_verifier (random string) and code_challenge (its SHA-256 hash) to the flow. The client sends the challenge with the auth request and the verifier with the token request. The auth server checks they match. This binds the code to the original requester even without a client secret. PKCE is now recommended for confidential clients too.

State parameter

The state parameter in the auth request is a CSRF defence. The client generates a random value, stores it in the user’s session, and includes it in the auth request. The auth server echoes it back on the redirect. The client must validate that the returned state matches what it stored.

Without state validation, an attacker can trick a victim into completing a flow that authorises the attacker’s account on the victim’s browser — or vice versa. State validation is mandatory; treat its absence as a bug.

state can also encode application context (e.g. the page the user was trying to reach), but that data should be opaque to the auth server and tamper-evident on return.

Token storage post-redirect

Once tokens land in the browser, where they live matters:

StorageProsCons
Cookies (HttpOnly, Secure, SameSite=Lax)Inaccessible to JavaScript, mitigates XSS exfiltration. Sent automatically with requests.Vulnerable to CSRF without additional defences. Size limits.
localStorageEasy to access from JS, no size pressure.Readable by any script on the page — XSS steals tokens trivially.
sessionStorageCleared on tab close.Same XSS exposure as localStorage.
In-memory onlyNo persistence to disk, gone on reload.Requires re-auth on every page reload.

For browser apps, the prevailing recommendation is HttpOnly cookies for session tokens, with the backend handling token storage and refresh. SPAs that must hold tokens client-side should at minimum use short access token lifetimes and rely on refresh via silent iframe or backend-for-frontend. Pair with Cloudflare WAF and Cloudflare for SaaS for edge protection.

Open redirect adjacent risks

Even with strict redirect_uri validation, related parameters can be abused:

  • Post-logout redirect URIs — same exact-match discipline applies, but often configured less carefully.
  • Application-level returnUrl parameters — if the app reads ?returnUrl=... and redirects without validation, that is a classic open redirect even though OAuth itself was correct.
  • state as a redirect carrier — encoding the post-login destination in state is fine, but the destination must still be validated against an allowlist before redirecting.

See also

References