SecurityCritical risk
How One Login Redirect Can Hand Over Your Whole App
Social login is quick to add and easy to get subtly wrong. Here's how redirect URIs, next= links and email linking can hand an account to someone else.
Adding "Sign in with Google" takes an afternoon. You paste a client ID, pick a callback URL, click the button, and you're in. It feels finished because it works.
Every social login ends with a redirect. Google, GitHub or Apple sends the person back to your app carrying a short-lived code that says "this is Jordan." If that code lands somewhere it shouldn't, and the protections below are missing, someone else can use it to get into Jordan's account. If Jordan is your admin, that's your whole app.
The IETF's OAuth 2.0 Security Best Current Practice (RFC 9700), published in January 2025, spells out where this goes wrong. It says pattern-based redirect checks turned out to be error-prone, that attacks on them have been observed in the wild, and that weak redirect validation lets an attacker obtain an authorization code or access token.
Redirect URI (or callback URL)
The address on your site where the provider sends people back, code attached. You register it in each provider's console.
Here are the six places we check first, and how to fix each one.
1. Your redirect list is looser than you think
RFC 9700 says authorization servers must use exact string matching for redirect URIs, with one narrow exception for localhost ports in native apps. Providers mostly follow that, but your settings still matter.
- Google requires the
redirect_urito exactly match one you authorized, down to the scheme, case and trailing slash. Its validation rules reject wildcards and open redirects. - GitHub lets you switch wildcard matching on or off for each callback URL. When it's on, codes can be sent to any subdomain or subfolder of that URL. Apps that had a single callback URL before August 3, 2026 have it switched on, and GitHub recommends turning it off if you don't need it.
- Supabase allows wildcards in its Redirect URLs allow list, but recommends the exact path in production and keeps the
**glob for local and preview URLs. - Apple keeps your return URLs on your Services ID, so check that list too.
The usual leftovers are http://localhost:3000/callback from day one and a wildcard added to make preview deploys work. Each one widens the set of places a login code can land. Keep the production client to production URLs, and give local and preview builds their own client or project.
2. Your app redirects wherever next= says
Most apps send people "back where they were" after login with something like ?next=, returnUrl or redirect_to. RFC 9700 mentions this exact pattern, then says clients must not expose open redirectors. An attacker can use one to pass login codes and tokens on to their own site, or to build a phishing link that starts with your real domain.
OWASP's first advice is to not take the destination URL from user input at all. Map short names to known pages instead. If you must accept a URL, parse it and compare the scheme, host and port against an allowlist. OWASP warns against raw prefix or suffix matching, so "starts with /" isn't a check.
// Same-site paths only; anything else goes to /dashboard.
const APP = 'https://yourapp.example'
function safeNext(value) {
try {
const url = new URL(value, APP)
return url.origin === APP ? url.pathname + url.search : '/dashboard'
} catch {
return '/dashboard'
}
}
3. Nothing ties the login to the browser that started it
Two small values do this job.
State is a random one-time value you save before sending someone to the provider, then check when they come back. GitHub's docs say that if it doesn't match, you should abort the process. Google says your client must prevent cross-site request forgery.
PKCE, defined in RFC 7636, adds a fresh secret to every login. Only a hash of it goes out with the request. Your app proves it holds the secret when it swaps the code for a session, so a code that leaks on its own isn't enough. RFC 9700 says public clients, like single-page and mobile apps, must use PKCE, and recommends it for server apps too. OWASP goes further: authorization code with PKCE for every client type, and no implicit flow that puts tokens in the URL.
Use the S256 method. RFC 7636 says clients that can use it must, and GitHub accepts only S256. On Supabase, set flowType: 'pkce'; its codes last five minutes and work once.
4. You read the ID token without checking it
With OpenID Connect, which Google's sign-in uses, you also get an ID token: a signed note saying who the person is. Decoding it isn't the same as verifying it. OpenID Connect Core says the issuer must exactly match, the audience must contain your client ID, the token mustn't be expired, and if you sent a nonce, the same nonce must come back. Google's guide also lists a signature check against its published keys.
Offer more than one provider? Then RFC 9700 says your app must defend against mix-up attacks, where a response from one provider gets passed off as another's. Remember which provider each login went to, and check the issuer on the way back.
Then key accounts on the right field. Google says don't use email as a unique identifier; use sub, which stays the same even if the person changes their address. OWASP recommends issuer and sub together.
5. Accounts merge because the emails match
Here's a quieter takeover. Someone signs up with your customer's email address before your customer does. Later the real customer arrives with "Sign in with Google," and your app helpfully joins the two accounts. The person who signed up first is still inside.
Security researchers Avinash Sudhodanan and Andrew Paverd call this account pre-hijacking. In their USENIX Security 2022 study, at least 35 of the 75 popular services they analyzed were vulnerable.
Supabase links identities that share an email automatically. Its docs say linking to an unverified address would be insecure, so it removes unconfirmed identities first. If you built linking yourself, OWASP is blunt: don't link accounts just because the emails match. Require a signed-in session on the existing account, and notify the user whenever a sign-in method is added or removed. Google's ID token includes email_verified, and GitHub's emails API marks each address as verified or not.
6. One stolen session can do everything
Even with all that fixed, plan for a session going missing one day, through a shared laptop or a bug you haven't found yet. Then the question is how much one session can do.
OWASP recommends asking for current credentials before sensitive changes and re-authenticating for high-risk actions like changing payment details. Put these behind a fresh sign-in and send an alert to the email already on file:
- Changing payout or bank details
- Exporting customer data in bulk
- Messaging or emailing all your users at once
- Adding an admin or changing the login email
- Linking a new sign-in method
An alert to the old address lets the real owner spot it and stop it.
Test it like an attacker would, on your own app
Where a pre-launch audit helps
Every one of these problems is invisible on the happy path. Login works for every honest user, so nobody notices until someone dishonest tries.
With your written permission first, a LaunchGate audit reviews your redirect settings, tests return parameters, state and PKCE handling, token checks, account linking and re-authentication on sensitive actions, then reports what we found with evidence and a recommended fix. We report on what we tested. LaunchGate isn't a law firm and doesn't give legal advice, so if a login flaw has already exposed customer data, ask an attorney what you need to do next.
For a first look, the Free Launch Check is a preliminary public-surface review — not a full audit. It only sees what's public, so it can't open your provider consoles or read your code. For that, compare the audit packages or read our methodology. Building on Supabase? Read 5 common RLS mistakes in production next.
Article checklist · 9 items
Check your own app
Tick each item once you have checked it on your app. Ticks stay in this browser only.
Your article check 0 / 9 complete
Want LaunchGate to check the rest?
Get a free launch checkThe Free Launch Check is a preliminary public-surface review — not a full audit.
References
- RFC 9700: Best Current Practice for OAuth 2.0 Security IETF (RFC Editor)
- RFC 7636: Proof Key for Code Exchange by OAuth Public Clients IETF (RFC Editor)
- OpenID Connect Core 1.0: ID Token Validation OpenID Foundation
- Unvalidated Redirects and Forwards Cheat Sheet OWASP Cheat Sheet Series
- OAuth 2.0 Cheat Sheet OWASP Cheat Sheet Series
- Authentication Cheat Sheet OWASP Cheat Sheet Series
- Using OAuth 2.0 for Web Server Applications Google for Developers
- OpenID Connect (Sign in with Google) Google for Developers
- Authorizing OAuth apps GitHub Docs
- REST API endpoints for emails GitHub Docs
- Configure Sign in with Apple for the web Apple Developer
- Redirect URLs Supabase Docs
- PKCE flow Supabase Docs
- Identity Linking Supabase Docs
- Pre-hijacked accounts: An Empirical Study of Security Failures in User Account Creation on the Web USENIX Security 2022
This article is general information, not legal advice. LaunchGate identifies issues based on the checks performed; it is not a law firm and does not provide legal advice. Some findings may require attorney or specialist review.