Skip to content
Security First — Part 8 of 30

OAuth Explained: Why 'Login with Google' Is Safer

Written by claude-sonnet-4 · Edited by claude-sonnet-4
oauthauthenticationsecuritylogin-with-googlecredential-stuffingpasswordstokensnextauthpythontypescript

Security First — Part 8 of 30


My friend Carlos runs a small e-commerce shop. A couple of years ago he built it with a freelancer — login page, checkout, the works. The developer stored passwords in the database using a hash algorithm that was already considered outdated. Nobody told Carlos. Nobody audited it.

In early 2025, a credential-stuffing attack hit his site. Hackers had purchased a combo list — a compiled file of 2 billion unique email/password pairs harvested from previous breaches — and systematically tried every combination against his login endpoint. Within hours, hundreds of customer accounts were compromised. The attackers weren't even targeting Carlos specifically. His store was just one of thousands swept up in an automated spray.

This is the world your app lives in. And it's getting worse.

According to Verizon's 2025 Data Breach Investigations Report, 22% of all data breaches now begin with stolen or compromised credentials — the single highest category of any attack vector. A Check Point analysis found a 160% increase in compromised credentials in 2025 compared to 2024. And in June 2025, researchers documented a dump of 16 billion passwords from aggregated breaches involving Google, Apple, and Facebook platforms — the largest credential-stuffing supply in history.

Carlos had done nothing obviously wrong. He'd just built his own auth system. That was the mistake.

Today I want to show you a better path: OAuth, and specifically why clicking "Login with Google" is not just more convenient — it's genuinely, structurally safer.


What OAuth Actually Is (in Plain English)

OAuth stands for Open Authorization. That second word is the key. OAuth is not about authentication (proving who you are) — it's about authorization (proving what you're allowed to do).

Think of it like a hotel key card system. You don't hand a stranger your house keys just because they need access to one room. The hotel gives them a temporary card that only opens their room, only during their stay. When they check out, the card stops working.

OAuth works the same way. When you click "Login with Google," you're not handing the app your Google password. You're asking Google to issue a temporary, limited-scope key card to the app on your behalf.

Here's the actual flow:

You → App: "I want to sign in with Google"
App → Google: "This user wants to sign in, here's my app ID"
Google → You: "Do you authorize this app to access your basic profile?"
You → Google: "Yes"
Google → App: [issues an access token]
App: Uses token to confirm your identity — never sees your Google password

The app gets a token. Not your password. That distinction is enormous.


The Technical Plumbing (Just Enough to Be Dangerous)

OAuth 2.0 is the current standard. When combined with OpenID Connect (OIDC), it also handles authentication — telling the app who you are, not just what you can do. That's what "Login with Google" actually uses.

Here's what a basic OAuth 2.0 login flow looks like in Python using a library like authlib:

# Install: pip install authlib requests flask
from authlib.integrations.flask_client import OAuth
from flask import Flask, redirect, url_for, session

app = Flask(__name__)
app.secret_key = 'your-secret-key'  # Use os.environ in production!

oauth = OAuth(app)
google = oauth.register(
    name='google',
    client_id='YOUR_GOOGLE_CLIENT_ID',
    client_secret='YOUR_GOOGLE_CLIENT_SECRET',
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid email profile'},
)

@app.route('/login')
def login():
    redirect_uri = url_for('callback', _external=True)
    return google.authorize_redirect(redirect_uri)

@app.route('/callback')
def callback():
    token = google.authorize_access_token()
    user_info = token.get('userinfo')
    # user_info contains: email, name, picture, sub (unique Google ID)
    # Store user_info['sub'] as your user identifier — NOT the email
    session['user'] = user_info
    return f"Logged in as {user_info['email']}"

And in TypeScript/Next.js using NextAuth:

// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'

const handler = NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    async session({ session, token }) {
      // Add user ID to session so you can identify them in your DB
      session.user.id = token.sub!
      return session
    },
  },
})

export { handler as GET, handler as POST }

That's it. No password column in your database. No bcrypt configuration. No "forgot password" email flow. No risk of storing credentials that can be stolen and cracked.

To set up Google OAuth credentials:

# 1. Go to console.cloud.google.com
# 2. Create a new project (or select existing)
# 3. Enable "Google+ API" or "Google Identity"
# 4. Go to Credentials → Create OAuth 2.0 Client ID
# 5. Set authorized redirect URI to: http://localhost:3000/api/auth/callback/google
# 6. Copy Client ID and Client Secret to your .env file

# .env
GOOGLE_CLIENT_ID=your-client-id-here.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret-here
NEXTAUTH_SECRET=run-this-to-generate: openssl rand -base64 32
NEXTAUTH_URL=http://localhost:3000

Why It's Structurally Safer: The Five-Layer Advantage

Let's be specific about what makes OAuth safer, not just hand-wavy "it's a big company so it's better."

1. You Never Store Passwords

The single biggest risk in Carlos's story wasn't that his passwords were hashed weakly. It was that he was storing passwords at all. With OAuth, your database has no passwords. An attacker who breaches your database gets emails and profile info — not credentials that can be used to log into anything else.

2. Credential Stuffing Fails by Design

Credential stuffing only works when users reuse passwords across sites. OAuth breaks this attack entirely. The user's "credential" to your app is a Google-issued token, not a password. Even if an attacker has a combo list of 2 billion email/password pairs, none of those passwords work on your app — because your app never accepted passwords in the first place.

3. Google's Security Infrastructure Works For You

When a user logs into your app via Google, they benefit from Google's entire threat detection stack: device trust signals, IP anomaly detection, impossible travel alerts, 2FA enforcement, passkey support, and decades of anti-abuse engineering. You get all of that for free.

4. Tokens Are Scoped and Revocable

The access token Google issues is limited to exactly the permissions you requested (openid email profile). It doesn't let the app read the user's Gmail, modify their Google Drive, or access any other Google service. And when a user wants to revoke access, they go to myaccount.google.com/permissions and disconnect your app. Done. The tokens are invalidated immediately.

5. Short-Lived by Default

OAuth access tokens typically expire in one hour. This is a feature, not a bug. If a token is somehow stolen, it has a limited useful life. Contrast this with a password, which is valid until the user changes it — which research shows most users rarely do.


The Nuance: OAuth Is Not Magic Armor

I want to be honest with you, because some of the security writing out there oversimplifies this. OAuth is safer for the password problem, but it introduces different risks you should know about.

The Drift/Salesforce breach (August 2025) is the cautionary tale here. Threat actor UNC6395 compromised a GitHub account, moved into Drift's AWS environment, extracted OAuth tokens from Drift's Salesforce integration, and used those tokens to access the Salesforce environments of 700+ organizations. The attackers needed no phishing, no exploits — just valid, long-lived OAuth tokens from a compromised third-party integration. The activity looked completely legitimate to monitoring systems.

The lesson isn't "don't use OAuth." The lesson is:

  • Treat OAuth tokens like passwords. They're credentials. Store them securely (encrypted at rest, never in plain text or .env files committed to git).
  • Audit third-party app connections regularly. In Google Workspace admin, in Salesforce, in your GitHub OAuth apps — check what has access and why.
  • Request minimum scopes. Your app probably doesn't need mail.read or files.readwrite. Only request what you actually need.
  • Watch for consent phishing. Attackers increasingly send fake OAuth consent screens that look legitimate. Train your users to check the app name and permissions before clicking "Allow."

Setting Up OAuth the Right Way: A Practical Checklist

If you're using AI tools like Cursor or Copilot to build your app, here's what to verify after it generates your auth code:

# Quick security audit for your OAuth setup
# 1. Check you're not requesting unnecessary scopes
grep -r "scope" src/auth/  # Should only see: openid email profile

# 2. Verify client secret is in environment variables, not hardcoded
grep -r "client_secret" src/  # Should return nothing (it's in .env)

# 3. Confirm redirect URIs are whitelisted (not wildcards)
# In Google Cloud Console → Credentials → OAuth 2.0 Client
# Authorized redirect URIs should be explicit, e.g.:
# https://yourapp.com/api/auth/callback/google
# NOT: https://yourapp.com/*  ← dangerous

# 4. Verify state parameter is being used (CSRF protection)
grep -r "state" src/auth/  # Should be present in your OAuth flow

Your Action Checklist

If you're building a new app:

  • Use OAuth (Google, GitHub, Microsoft) instead of username/password auth
  • Use a battle-tested library: NextAuth.js, Authlib, Passport.js, Auth0 — don't write OAuth flows from scratch
  • Request only the minimum scopes you need (openid email profile is usually enough)
  • Store your CLIENT_SECRET in environment variables, never in code
  • Set explicit redirect URIs in your OAuth app settings — no wildcards
  • Use sub (the unique user ID Google provides) as your internal user identifier, not email (emails can change)

If you're using AI tools to build auth:

  • After generation, grep your codebase for client_secret to confirm it's not hardcoded
  • Check that scopes are minimal
  • Review the redirect URI configuration in your OAuth provider dashboard
  • Ask your AI assistant: "Is there anything in this auth implementation that's not security best practice?"

For your existing apps:

  • Audit which third-party apps have OAuth access to your accounts (Google: myaccount.google.com/permissions, GitHub: Settings → Applications)
  • Revoke any apps you don't recognize or no longer use
  • If you're currently storing passwords, plan a migration to OAuth — it's a significant security upgrade

Ask The Guild

This week's community question:

Have you run into an OAuth implementation that went sideways — either code your AI generated that had a security issue, or a third-party app integration you later regretted? What did you find, and how did you fix it?

Share your experience in the #security-first channel. The best contributions get featured in next week's edition.


Tom Hundley is a software architect with 25 years of experience. He teaches security fundamentals to the next generation of vibe coders through the AI Coding Guild's Security First series.

Copy A Prompt Next

Start safely

If this article changed how you think about the problem, copy a prompt that turns that judgment into one safe, reviewable next step.

Matching public prompts

6

Keep the task scoped, copy the prompt, then inspect one reviewable diff before the agent continues.

Need the safest first move instead? Open the curated sample prompts before you browse the broader library.

Start Here — Build Safely With AIStart Here — Build Safely With AI

Choose a Tiny First Win

How to pick a first project that teaches the workflow without dragging you into complex product and engineering problems.

Preview
"I need help shrinking this idea into a safe first vibe-coded project.
The big idea is: [describe idea]
Reduce it to the smallest useful version by:
1. removing anything that requires auth, billing, production data, or complicated integrations
2. keeping only one user and one core job to be done
Security First

Turn this security lesson into a repeatable review habit

This article gives you the judgment call. The security paths give you the vocabulary, checklists, and repetition to catch the next issue before it reaches users.

Best Next Path

Identity and Authentication Deep Dive

Guild Member · $29/mo

Go deep on sessions, JWTs, OAuth flows, enterprise identity, and the auth mistakes that AI-generated code keeps repeating.

15 lessonsIncluded with the full Guild Member library

Need the free route first?

Start with Start Here — Build Safely With AI if you want the workflow and vocabulary before you dive into the deeper path above.

T

About Tom Hundley

Tom Hundley writes for builders who need stronger technical judgment around AI-assisted software work. The Guild turns production experience into public articles, copy-paste prompts, and structured learning paths that help non-software developers supervise AI agents more safely.

Do this next

Leave this article with one concrete move. Copy the matching prompt, or start with the path that teaches the safest next skill in sequence.