AI Implements OAuth from Stack Overflow Circa 2015
AI generates OAuth with: the Implicit flow (token in URL fragment — deprecated since 2019), client secret in frontend code (visible in browser DevTools), no state parameter (CSRF vulnerable), auto-merge on email match (account takeover vector), over-scoped permissions (requests read+write when only read is needed), and no token exchange validation (accepts any callback). Every pattern is a known vulnerability with a standardized fix.
Modern OAuth is: Authorization Code flow (code exchanged server-side), PKCE-protected (Proof Key for Code Exchange — even public clients are secure), state-validated (random state parameter prevents CSRF), carefully scoped (request minimal permissions), server-side token exchange (client secret never leaves the server), and safe account linking (verify ownership before merge). AI generates none of these.
These rules cover: Authorization Code + PKCE flow, state parameter validation, server-side token exchange, minimal scopes, and secure account linking strategies.
Rule 2: State Parameter for CSRF Protection
The rule: 'Generate a cryptographically random state parameter for every OAuth request. Store it in the session (server-side) or a secure httpOnly cookie. On callback, verify that the returned state matches the stored state. Reject the callback if state is missing or mismatched. This prevents CSRF attacks where an attacker tricks the user into completing an OAuth flow that links the attacker account.'
For the attack without state: 'Attacker starts OAuth flow with their own account, gets the authorization code, then tricks the victim into visiting the callback URL with the attacker code. The victim app links the attacker provider account to the victim user account. Now the attacker can log in as the victim using their own provider credentials. The state parameter breaks this — the callback state will not match the victim session.'
AI generates: redirect to provider with no state parameter, callback handler that accepts any response. The CSRF attack is trivial and gives the attacker full account access. One random string, stored in the session, verified on callback — three lines of code that prevent account takeover.
Without the state parameter: attacker starts OAuth with their account, tricks victim into visiting the callback URL. Victim app links attacker provider to victim account. Attacker logs in as victim. One random string in the session prevents this entirely.
Rule 3: Server-Side Token Exchange and Minimal Scopes
The rule: 'Exchange the authorization code for tokens on your server, not in the browser. The token exchange requires the client secret (for confidential clients) — this must never be in frontend code. Send the code + code_verifier + client_secret from your backend to the provider token endpoint over HTTPS. Store the resulting tokens server-side or in httpOnly cookies.'
For scope minimization: 'Request the minimum scopes needed: openid profile email for social login (not repo, admin, or write access). Each additional scope is: a permission the user must approve (friction), data you must protect (liability), and an attack surface (if your tokens are stolen, the damage is limited to granted scopes). Review scopes quarterly — remove any that are no longer used.'
AI generates: scope: 'user repo admin' — requesting full repository and admin access for a login button. The user sees a scary permission dialog, conversion drops. If tokens are stolen, the attacker has admin access to all repos. Minimal scopes: less friction, less liability, less blast radius.
Rule 4: Secure Account Linking
The rule: 'Never auto-merge accounts based on email address alone. When a user signs in with a provider (Google) and an account with that email exists (from password signup), require verification of the existing account before linking. Options: (1) prompt for the existing account password, (2) send a verification email to confirm ownership, (3) require the user to link accounts from within the authenticated session.'
For the attack without verification: 'Attacker creates a Google account with victim-email@example.com (possible with some providers). Attacker signs in with OAuth. App finds existing account with that email and auto-merges. Attacker now has full access to the victim account — created through password signup, stolen through unverified OAuth linking. This is a well-documented account takeover pattern.'
AI generates: const user = await db.users.findOne({ email: profile.email }); if (user) linkAccount(user, providerAccount); — auto-merge with zero verification. The account takeover requires only knowing the victim email. Verification before linking adds one step for legitimate users and completely prevents this attack vector.
- Never auto-merge on email — require verification of existing account ownership
- Options: password prompt, verification email, or link from authenticated session
- Allow multiple providers per account — but each link requires explicit user consent
- Log all account linking events — who, which provider, when, from which IP
- Allow unlinking providers — but require at least one auth method remains active
Attacker creates a Google account with victim@example.com, signs in via OAuth, app auto-merges on email match — attacker owns the victim account. Email alone is not proof of identity. Require verification before linking accounts from different auth methods.
Rule 5: OAuth Error Handling and Edge Cases
The rule: 'Handle every OAuth callback scenario: success (code present), user denied (error=access_denied), provider error (error=server_error), state mismatch (CSRF attempt), and expired code (too slow to exchange). Each scenario needs a specific user-facing response — not a generic error page. Log all failures with the error code and description for debugging.'
For token refresh: 'OAuth access tokens expire. Store the refresh token securely and use it to get new access tokens before they expire. Not all providers issue refresh tokens by default — Google requires access_type=offline and prompt=consent on the first authorization. If the refresh token is revoked (user removed your app from their account), redirect to re-authorization gracefully.'
AI generates: callback handler that only handles the success case. User denies permission — unhandled. Provider returns an error — 500. State mismatch — ignored. Every edge case is a user stuck on an error page or, worse, a security vulnerability. Five conditionals in the callback handler cover all scenarios.
Complete OAuth Integration Rules Template
Consolidated rules for OAuth integration.
- Authorization Code + PKCE — always, all client types, never Implicit flow
- Cryptographic state parameter — stored server-side, verified on callback
- Server-side token exchange — client secret never in frontend code
- Minimal scopes — openid profile email for login, nothing more unless required
- No auto-merge on email — verify existing account ownership before linking
- Handle all callback scenarios — denied, error, expired, state mismatch
- Store refresh tokens securely — handle revocation and re-authorization gracefully
- Use established libraries (NextAuth, Passport.js) — never implement OAuth from scratch