惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
Google DeepMind News
Google DeepMind News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
小众软件
小众软件
Blog — PlanetScale
Blog — PlanetScale
腾讯CDC
A
About on SuperTechFans
Vercel News
Vercel News
I
InfoQ
阮一峰的网络日志
阮一峰的网络日志
月光博客
月光博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
S
SegmentFault 最新的问题
V
Visual Studio Blog
T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
M
MIT News - Artificial intelligence
博客园 - 【当耐特】
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Azure Blog
Microsoft Azure Blog
Apple Machine Learning Research
Apple Machine Learning Research
GbyAI
GbyAI
美团技术团队

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Stop Blindly Trusting Passport.js: How to Implement Secur...
Pau Dang · 2026-05-18 · via DEV Community

OAuth 2.0 is the backbone of modern authentication. But many developers treat it as a "set it and forget it" feature by using libraries like Passport.js. While these libraries are great, they often hide the critical security handshake happening under the hood—leaving your app vulnerable to OAuth CSRF attacks.

In this post, I'll show you how we implemented a Zero-Trust Social Login flow in nodejs-quickstart-structure v2.2.1 using plain Node.js and Axios.

The Attack: How a Hacker Steals Your Identity

Most developers know about standard CSRF (Cross-Site Request Forgery). OAuth CSRF is a specific variant where an attacker tricks a victim into linking the attacker's social account to the victim's application account.

Here is exactly how the attack happens:

  1. The Attacker's Setup: A hacker visits your site and clicks "Link Google." They log into their own Google account, but when Google redirects them back to your site, they stop and copy the code from the URL.
  2. The Phish: The hacker sends a victim a link: https://your-app.com/api/auth/google/callback?code=HACKER_CODE.
  3. The Click: The victim (already logged into your app) clicks the link.
  4. The Link: Your server receives the HACKER_CODE, validates it with Google, and sees it's a valid account. Since the victim is the one who sent the request, your server links the hacker's Google ID to the victim's user profile.
  5. The Takeover: The hacker can now simply click "Login with Google" on your site and they are instantly logged in as the victim.

The result? A quiet, total account takeover.

The Attack Flow

The Solution: The state Parameter

The OAuth2 spec provides a state parameter specifically to prevent this. Here is how it looks:

The Secure Flow

Step 1: Generate and Store a Secure State

When the user clicks "Login with Google," don't just redirect them. Generate a random string, store it in a secure cookie, and pass it to the provider.

// authController.js
async googleLogin(req, res) {
  const state = crypto.randomBytes(16).toString('hex');

  // Store state in an HttpOnly, SameSite=Lax cookie
  res.cookie('oauth_state', state, { 
    httpOnly: true, 
    secure: true, // Always use HTTPS in production
    sameSite: 'lax',
    maxAge: 10 * 60 * 1000 // Valid for 10 minutes
  });

  const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` + 
    new URLSearchParams({
      client_id: process.env.GOOGLE_CLIENT_ID,
      redirect_uri: process.env.GOOGLE_CALLBACK_URL,
      response_type: 'code',
      scope: 'profile email',
      state: state // <--- The magic happens here
    }).toString();

  res.redirect(authUrl);
}

Enter fullscreen mode Exit fullscreen mode

Step 2: Verify the State on Callback

When Google redirects the user back to your site, the first thing you must do is check if the state in the URL matches the state in your cookie.

async googleCallback(req, res) {
  const { code, state } = req.query;
  const savedState = req.cookies?.oauth_state;

  // Always clear the cookie immediately to prevent reuse
  res.clearCookie('oauth_state');

  if (!state || state !== savedState) {
    return res.status(403).send('Security Alert: State mismatch detected!');
  }

  // Now it's safe to exchange the code for a token
  const tokenResponse = await axios.post('https://oauth2.googleapis.com/token', {
    code,
    client_id: process.env.GOOGLE_CLIENT_ID,
    client_secret: process.env.GOOGLE_CLIENT_SECRET,
    redirect_uri: process.env.GOOGLE_CALLBACK_URL,
    grant_type: 'authorization_code'
  });

  // Handle user data...
}

Enter fullscreen mode Exit fullscreen mode

Why This Manual Approach Wins

By ditching "black-box" libraries and using a deterministic flow with Axios, you get:

  1. Full Auditability: You can log exactly what was sent and received.
  2. Explicit Security: You can't "forget" to validate the state because you are writing the logic yourself.
  3. Leaner App: No need for heavy middleware if you only need social login.

Want a Battle-Tested Template?

Implementing this correctly across every project is tedious. That's why I built the Node.js Quickstart Generator.

If you want to see this implementation in action, check out these resources:

If you found this helpful, check out the repo and give it a ⭐!

GitHub Repo: nodejs-quickstart-structure