OAuth Explained: Why 'Login with Google' Is Safer
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
.envfiles 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.readorfiles.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 profileis usually enough) - Store your
CLIENT_SECRETin 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_secretto 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.