Google Login in Express with PassportJS & JWT This article provides a step-by-step guide on implementing Google OAuth 2.0 authentication in an Express.js application using PassportJS and JSON Web Tokens (JWT). It covers setting up the Google strategy in Passport, configuring the user model with required fields like `googleId` and `refreshToken`, and creating authentication routes with callback handling. The tutorial also includes instructions for generating JWT tokens, managing cookies, and deploying the solution in production with secure cookie settings. ⚡ Quick OAuth + JWT Architecture For Fast Revision When handling social logins while maintaining a stateless JWT ecosystem, follow this flow: php User --- 1. GET /auth/google --- Passport Engine --- Redirects to Google Sign-In User <--- 2. Grants Permission -- Google Server Backend Callback <-- 3. Code/Profile Handshake <-- Google Server Verifies & Upserts User Profile User <--- 4. Sets Secure Access & Refresh Cookies --- Backend Controller Generates Custom JWTs Core Strategy Rules No Server-Side Sessions: We explicitly disable passport session serialization session: false because our app uses stateless JWT tokens. User Accounts Linking: If a user registers normally with an email address and later hits the "Sign In with Google" button, we automatically link the identity by pinning the googleId onto the pre-existing document profile. Prerequisites & Dependencies 📂 Project Structure └── src/ ├── config/ ├── controllers/ ├── middlewares/ ├── models/ ├── routes/ ├── utils/ ├── app.ts └── index.ts ├── .env 📥 Install Required Packages Execute the following installation string to fetch Passport.js, the Google OAuth2.0 strategy token extensions, and their respective type-hint definitions: npm install passport passport-google-oauth20 jsonwebtoken cookie-parser bcrypt mongoose npm install -D @types/passport-google-oauth20 Step 1: Cloud Console Configurations Before writing software, you need application credentials from the Google Cloud Dashboard. Navigate to the . Google Cloud Console Create a New Project using the project selection drop-down layout. Configure your OAuth Consent Screen and designate the publishing status as External . Head to the Clients page, choose Create Clients , and click OAuth Client ID . Set the application type to Web Application and add your explicit callback mapping: - Authorized Redirect URIs: http://localhost:3000/api/v1/auth/google/callback - Save your changes and copy your Client ID and Client Secret tokens. 🌐 Environment Setup Append these key-value configurations to your root .env file environment block: GOOGLE CLIENT ID=your google client id here GOOGLE CLIENT SECRET=your google client secret here GOOGLE CALLBACK URL=http://localhost:3000/api/v1/auth/google/callback Step 2: Adapting the Database Schema To support alternative OAuth logins alongside traditional password profiles, update your Mongoose Model configuration. 🔒 Critical Security Rule When integrating third-party OAuth provider chains, youmust make your schema's password string optional required: false . This allows users who signed up with Google to create accounts without passwords. Make sure these key fields are mapped out in your user schema definitions: js const userSchema = new Schema { username: { type: String, required: true, unique: true }, email: { type: String, required: true, unique: true }, // Make password optional for OAuth registrations password: { type: String, required: false }, avatarUrl: { type: String }, refreshToken: { type: String }, googleId: { type: String } // Keeps track of mapped Google profiles } ; To maintain security, place an evaluation guard inside your traditional login controllers so social-only accounts cannot be hijacked through brute-force attempts: if user.password { throw new ApiError 400, "This account was registered via Google Sign-In. Please log in using Google." ; } Step 3: Architecting the Passport Strategy Now we configure Passport to handle Google authentication. Here we: - receive the Google profile - check if the user already exists - create a new account if needed - link existing accounts using email python import passport from "passport"; import { Strategy as GoogleStrategy } from "passport-google-oauth20"; import { User } from "../models/User.model.js"; import { generateUsername } from "../utils/usernameGen.js"; passport.use new GoogleStrategy { clientID: process.env.GOOGLE CLIENT ID , clientSecret: process.env.GOOGLE CLIENT SECRET , callbackURL: process.env.GOOGLE CALLBACK URL, }, async accessToken, refreshToken, profile, done = { try { const email = profile.emails?. 0 ?.value; if email { return done new Error "Google account profile must yield a primary email address" ; } // Look for an existing account matching either the googleId OR the email address let user = await User.findOne { $or: { googleId: profile.id }, { email } , } ; // Case 1: Account exists but lacks a googleId link First-time social login for an existing user if user && user.googleId { user.googleId = profile.id; await user.save ; } // Case 2: No account exists under this email - Create a brand new user profile if user { const uniqueUsername = await generateUsername profile.displayName ; user = await User.create { username: uniqueUsername, fullName: profile.displayName, email, googleId: profile.id, avatarUrl: profile.photos?. 0 ?.value || "", } ; } // Remove sensitive fields before returning the user const sanitizedUser = await User.findById user. id .select "-password -refreshToken -googleId" ; if sanitizedUser { return done new Error "User not found after creation" ; } return done null, sanitizedUser ; } catch error { return done error as Error ; } } ; export default passport; Dynamic Namespace Deduplication Utility When creating users via OAuth, Google provides full display names, which aren't guaranteed to be unique, so we generate a fallback username if needed.: src/utils/usernameGen.ts js import { User } from "../models/User.model.js"; export const generateUsername = async displayName: string : Promise