Auth Redirect Attacks: How Central Login Flows Get Exploited
December 2025 · Derick Zr · 5 minutes read
I built centralized auth. Felt good about it.
Then I ran a security review. Found four ways an attacker could steal user sessions.
Small mistakes. Catastrophic consequences.
In the previous article, I showed how to build centralized auth. This is about the bugs that get you breached.
Real attacks. Real fixes.
Threat Model (One Sentence)
Attackers want your users' authorization codes and sessions.
If they can control where you redirect, what client you think is calling, or what servers you fetch, they can steal them.
Open Redirect: I Built an Attack Vector
I accepted the redirect_uri from the request. Seemed fine.
Attacker sent this:
/authorize?client_id=appA&redirect_uri=https://attacker.com/callbackUser logs in. My auth service redirects to the attacker's domain. With the authorization code in the URL.
Attacker now has the code.
The fix:
- Store allowed redirect URIs per client in a database
- Only allow exact matches against the registered list
- Reject wildcard patterns by default
- Parse and normalize before comparing
- Never accept a full redirect URL from the browser unless it matches a registered value
- Bind the issued code to the
client_idthat requested it
Demo: Open Redirect Steals the Code
Open Redirect Attack
See how accepting arbitrary redirect URIs lets attackers steal authorization codes
Registered Allowlist:
Prevention:
- Store allowed redirect URIs per client in database
- Only allow exact matches (no wildcards by default)
- Parse and normalize URLs before comparing
- Bind issued codes to the client_id and redirect_uri
I Didn't Bind Codes to Clients
I thought locking codes to redirect URIs was enough.
It wasn't.
My code exchange endpoint checked if the code existed and wasn't expired. That was it.
An attacker started a flow with AppA. Got a code. Then exchanged it using AppB's credentials.
The code worked. My server didn't care which client was exchanging it.
The moment I caught it
I was testing client isolation. Grabbed a code from one app's callback. Sent it to another app's exchange endpoint.
It succeeded.
The fix
Lock each code to the client_id that requested it. When AppB tries to exchange a code issued to AppA, reject it.
Make codes single-use. 60-second expiration. Check the redirect URI at authorization time and the client ID at exchange time.
For public clients (mobile apps, SPAs), use PKCE. It binds the code to a secret the client generates before the flow starts.
Demo: Client/Origin Mismatch (Confused Deputy)
Confused Deputy Attack
See how missing client_id validation lets one app exchange codes meant for another
Step 1: Code Issued
xyz789abcappAhttps://appA.com/callbackPrevention:
- Bind each code to client_id at issuance
- Validate client_id matches at exchange time
- One-time codes with short TTL (60 seconds)
- Use PKCE for public clients
I Added a Server-Side Fetch Without Thinking
I wanted to show client logos in the auth flow. Made it configurable.
Added a logo_url parameter. My server would fetch it and display it on the consent screen.
Seemed innocent.
Then I saw this request:
/authorize?client_id=appA&logo_url=http://169.254.169.254/latest/meta-data/That's AWS's metadata service. Internal only. Not accessible from the internet.
But my auth server could reach it.
An attacker sent that URL. My server fetched it. Returned cloud credentials, instance metadata, internal network info.
I turned my auth service into a network scanner for attackers.
The fix
Don't fetch user-controlled URLs during auth flows. Period.
If you must fetch, allowlist specific hosts and schemes. Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and link-local addresses (169.254.0.0/16).
Resolve DNS first and validate the IP. This stops DNS rebinding attacks where a domain resolves to an internal IP after you check it.
Set short timeouts. Limit response size. Don't follow redirects.
Demo: SSRF Against Internal Network
SSRF Attack
See how accepting user-controlled URLs lets attackers scan internal networks
Allowed Hosts:
Prevention:
- Don't server-fetch user-controlled URLs
- If required: allowlist hosts and schemes
- Block private IP ranges (10.x, 192.168.x, 127.x, 169.254.x)
- Resolve DNS and validate resolved IP
- Enforce timeouts and size limits
The Code Leaked Into Analytics Logs
My callback page had Google Analytics. Standard practice, right?
User logs in. Gets redirected to /callback?code=abc123. Page loads. Analytics script fires.
The browser sends a request to Google's servers. Includes the full URL as the Referer header.
Referer: https://myapp.com/callback?code=abc123
That code is now sitting in Google's logs. If it's still valid, anyone with access to those logs can exchange it.
I found this during a security audit. Checked the network tab. Saw third-party requests happening before code exchange.
Every analytics hit, every CDN image, every third-party script got the code in the referrer.
The fix
Exchange the code on the server before rendering anything. Don't send HTML with third-party resources until the code is consumed and gone.
Set Referrer-Policy: no-referrer or strict-origin on callback routes.
Make codes single-use with a 60-second TTL. Even if they leak, they expire fast and can't be reused.
Demo: Referrer Leak
Referrer Leak Attack
See how authorization codes leak to third parties via the Referer header
Callback Page URL:
https://app.example.com/callback?code=xyz789abcThis page loads external resources (analytics, scripts, images)
Third-Party Resource:
https://analytics.thirdparty.com/track.jsPrevention:
- Set Referrer-Policy: no-referrer on callback pages
- Or use Referrer-Policy: strict-origin (domain only)
- Avoid third-party resources on callback routes
- Exchange codes immediately server-side
- Keep codes short-lived and one-time use
How to set the policy:
Referrer-Policy: no-referrerAdd this header to callback route responses, or use a meta tag in HTML.
What Actually Matters
- Redirect URIs are registered and exact-matched per client
- Codes are bound to
client_idand redirect URI - Codes are one-time and short-lived
- Code exchange validates audience/client every time
- Auth service does not fetch user-controlled URLs (or uses strict allowlists + IP blocking)
- Callback routes avoid third-party loads and set a strict referrer policy
Key Takeaways
Redirects kill more auth systems than bad crypto.
Fix where you're sending users before you worry about algorithm choices.
Every input in an auth flow is hostile until proven otherwise.
Client IDs, redirect URIs, logo URLs. Validate everything.
Bind codes like your system depends on it.
Lock them to the client, the redirect URI, and a short expiration. Make them single-use.