Google OAuth Integration in Node.js with Code Examples (2025 Guide)

Google OAuth Integration in Node.js with Code Examples (2025 Guide)

|By Malik Saqib

Authentication is the backbone of modern web applications, and Google OAuth has become one of the most trusted and user-friendly authentication methods available. If you've ever clicked "Sign in with Google" on a website, you've experienced OAuth in action. In this comprehensive guide, we'll walk through implementing Google OAuth 2.0 in your Node.js applications from scratch.

What is Google OAuth and Why Should You Use It?

OAuth 2.0 is an authorization framework that allows third-party applications to access user data without exposing passwords. When you integrate Google OAuth into your Node.js application, you're leveraging Google's robust authentication infrastructure, which means:

  • Enhanced Security: No need to store user passwords
  • Better User Experience: Users can sign in with accounts they already have
  • Reduced Development Time: Let Google handle the complex authentication logic
  • Trust Factor: Users trust Google's authentication system

According to recent studies, applications with social login options see up to 20% higher conversion rates compared to traditional email/password registration forms.

Prerequisites

Before we dive into the code, make sure you have:

  • Node.js (v16 or higher) installed on your machine
  • Basic understanding of Express.js
  • A Google account for accessing Google Cloud Console
  • npm or yarn package manager

Setting Up Your Google OAuth Credentials

The first step is creating OAuth credentials in the Google Cloud Console. This is where your application gets its identity.

Step 1: Create a Google Cloud Project

  1. Navigate to the Google Cloud Console
  2. Click on "Select a Project" and then "New Project"
  3. Give your project a meaningful name like "my-nodejs-oauth-app"
  4. Click "Create"

Step 2: Enable Google+ API

  1. In your project dashboard, go to "APIs & Services" > "Library"
  2. Search for "Google+ API" or "Google People API"
  3. Click on it and press "Enable"

Step 3: Create OAuth 2.0 Credentials

  1. Go to "APIs & Services" > "Credentials"
  2. Click "Create Credentials" and select "OAuth client ID"
  3. Configure the OAuth consent screen if prompted
  4. Choose "Web application" as the application type
  5. Add authorized redirect URIs (e.g., http://localhost:3000/auth/google/callback)
  6. Click "Create"

You'll receive a Client ID and Client Secret. Keep these safe—we'll need them in our application.

Building the Node.js Application

Let's build a complete authentication system step by step.

Step 1: Initialize Your Project

mkdir google-oauth-nodejs
cd google-oauth-nodejs
npm init -y

Step 2: Install Required Dependencies

npm install express express-session passport passport-google-oauth20 dotenv

Here's what each package does:

  • express: Web framework for Node.js
  • express-session: Session middleware for maintaining user sessions
  • passport: Authentication middleware for Node.js
  • passport-google-oauth20: Passport strategy for Google OAuth 2.0
  • dotenv: Loads environment variables from .env file

Step 3: Create Environment Variables

Create a .env file in your project root:

GOOGLE_CLIENT_ID=your_client_id_here
GOOGLE_CLIENT_SECRET=your_client_secret_here
SESSION_SECRET=your_random_session_secret
CALLBACK_URL=http://localhost:3000/auth/google/callback
PORT=3000

Important: Never commit your .env file to version control. Add it to your .gitignore file.

Step 4: Set Up Passport Configuration

Create a file called config/passport.js:

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
 
passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: process.env.CALLBACK_URL,
    },
    async (accessToken, refreshToken, profile, done) => {
      try {
        // Here you would typically:
        // 1. Check if user exists in your database
        // 2. If not, create a new user
        // 3. Return the user object
        
        const user = {
          googleId: profile.id,
          email: profile.emails[0].value,
          name: profile.displayName,
          picture: profile.photos[0].value,
        };
        
        // For demo purposes, we're just returning the profile
        // In production, you'd save this to a database
        return done(null, user);
      } catch (error) {
        return done(error, null);
      }
    }
  )
);
 
// Serialize user for the session
passport.serializeUser((user, done) => {
  done(null, user);
});
 
// Deserialize user from the session
passport.deserializeUser((user, done) => {
  done(null, user);
});
 
module.exports = passport;

Step 5: Create the Main Server File

Create server.js:

require('dotenv').config();
const express = require('express');
const session = require('express-session');
const passport = require('./config/passport');
 
const app = express();
 
// Session configuration
app.use(
  session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
      secure: process.env.NODE_ENV === 'production', // Use secure cookies in production
      maxAge: 24 * 60 * 60 * 1000, // 24 hours
    },
  })
);
 
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
 
// Middleware to check if user is authenticated
const isAuthenticated = (req, res, next) => {
  if (req.isAuthenticated()) {
    return next();
  }
  res.redirect('/');
};
 
// Routes
app.get('/', (req, res) => {
  res.send(`
    <html>
      <head>
        <title>Google OAuth Demo</title>
        <style>
          body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          }
          .container {
            text-align: center;
            background: white;
            padding: 40px;
            border-radius: 10px;
            box-shadow: 0 10px 25px rgba(0,0,0,0.2);
          }
          a {
            display: inline-block;
            margin-top: 20px;
            padding: 12px 30px;
            background: #4285f4;
            color: white;
            text-decoration: none;
            border-radius: 5px;
            font-weight: bold;
          }
          a:hover {
            background: #357ae8;
          }
        </style>
      </head>
      <body>
        <div class="container">
          <h1>Welcome to Google OAuth Demo</h1>
          <p>Click below to sign in with your Google account</p>
          <a href="/auth/google">Sign in with Google</a>
        </div>
      </body>
    </html>
  `);
});
 
// Google OAuth routes
app.get(
  '/auth/google',
  passport.authenticate('google', {
    scope: ['profile', 'email'],
  })
);
 
app.get(
  '/auth/google/callback',
  passport.authenticate('google', { failureRedirect: '/' }),
  (req, res) => {
    res.redirect('/profile');
  }
);
 
// Protected route
app.get('/profile', isAuthenticated, (req, res) => {
  res.send(`
    <html>
      <head>
        <title>Profile</title>
        <style>
          body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          }
          .profile-card {
            background: white;
            padding: 40px;
            border-radius: 10px;
            box-shadow: 0 10px 25px rgba(0,0,0,0.2);
            text-align: center;
            max-width: 400px;
          }
          img {
            border-radius: 50%;
            width: 100px;
            height: 100px;
            margin-bottom: 20px;
          }
          .logout-btn {
            display: inline-block;
            margin-top: 20px;
            padding: 10px 25px;
            background: #ea4335;
            color: white;
            text-decoration: none;
            border-radius: 5px;
          }
          .logout-btn:hover {
            background: #c5362d;
          }
        </style>
      </head>
      <body>
        <div class="profile-card">
          <img src="${req.user.picture}" alt="Profile Picture">
          <h2>${req.user.name}</h2>
          <p>${req.user.email}</p>
          <p><strong>Google ID:</strong> ${req.user.googleId}</p>
          <a href="/logout" class="logout-btn">Logout</a>
        </div>
      </body>
    </html>
  `);
});
 
// Logout route
app.get('/logout', (req, res) => {
  req.logout((err) => {
    if (err) {
      return next(err);
    }
    res.redirect('/');
  });
});
 
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

Testing Your OAuth Integration

  1. Start your server:
node server.js
  1. Open your browser and navigate to http://localhost:3000
  2. Click "Sign in with Google"
  3. You'll be redirected to Google's login page
  4. After successful authentication, you'll be redirected back to your profile page

Adding Database Integration

In a real-world application, you'll want to store user information in a database. Here's an example using MongoDB with Mongoose:

npm install mongoose

Update your config/passport.js to include database operations:

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const mongoose = require('mongoose');
 
// User model
const UserSchema = new mongoose.Schema({
  googleId: {
    type: String,
    required: true,
    unique: true,
  },
  email: {
    type: String,
    required: true,
  },
  name: String,
  picture: String,
  createdAt: {
    type: Date,
    default: Date.now,
  },
});
 
const User = mongoose.model('User', UserSchema);
 
passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: process.env.CALLBACK_URL,
    },
    async (accessToken, refreshToken, profile, done) => {
      try {
        // Check if user already exists
        let user = await User.findOne({ googleId: profile.id });
 
        if (!user) {
          // Create new user
          user = await User.create({
            googleId: profile.id,
            email: profile.emails[0].value,
            name: profile.displayName,
            picture: profile.photos[0].value,
          });
        }
 
        return done(null, user);
      } catch (error) {
        return done(error, null);
      }
    }
  )
);
 
passport.serializeUser((user, done) => {
  done(null, user.id);
});
 
passport.deserializeUser(async (id, done) => {
  try {
    const user = await User.findById(id);
    done(null, user);
  } catch (error) {
    done(error, null);
  }
});
 
module.exports = passport;

Add MongoDB connection to your server.js:

const mongoose = require('mongoose');
 
// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/oauth-demo', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
.then(() => console.log('MongoDB connected'))
.catch(err => console.error('MongoDB connection error:', err));

Best Practices and Security Considerations

1. Environment Variables

Always store sensitive information in environment variables, never in your codebase. Use different credentials for development and production environments.

2. HTTPS in Production

Always use HTTPS in production. OAuth requires secure connections to protect user data during the authentication flow.

3. Token Storage

Never store access tokens in localStorage or sessionStorage on the client side. Keep them server-side in secure, HTTP-only cookies or session storage.

4. Scope Minimization

Only request the permissions (scopes) your application actually needs. For basic authentication, profile and email are usually sufficient.

5. Session Security

Configure session cookies properly:

session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true, // Requires HTTPS
    httpOnly: true, // Prevents XSS attacks
    maxAge: 24 * 60 * 60 * 1000, // 24 hours
    sameSite: 'lax', // CSRF protection
  },
})

6. Error Handling

Implement comprehensive error handling:

app.get(
  '/auth/google/callback',
  passport.authenticate('google', { 
    failureRedirect: '/login',
    failureMessage: true 
  }),
  (req, res) => {
    res.redirect('/profile');
  }
);
 
// Error handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something went wrong!');
});

Common Issues and Troubleshooting

Issue 1: Redirect URI Mismatch

Error: redirect_uri_mismatch

Solution: Ensure the callback URL in your Google Cloud Console exactly matches the one in your application code, including the protocol (http/https) and port.

Issue 2: Invalid Client Error

Error: invalid_client

Solution: Double-check your Client ID and Client Secret in your .env file. Make sure there are no extra spaces or quotes.

Issue 3: Session Not Persisting

Problem: User gets logged out after page refresh

Solution: Verify that:

  • You're using express-session correctly
  • Session secret is set
  • You've called passport.session() after initializing sessions

Issue 4: Cookies Not Working

Problem: Session cookies aren't being set

Solution: Check your cookie configuration. If testing locally without HTTPS, set secure: false in development.

Advanced Features

Refresh Token Implementation

To keep users logged in longer, implement refresh token logic:

passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: process.env.CALLBACK_URL,
      accessType: 'offline', // Request refresh token
      prompt: 'consent', // Force consent screen
    },
    async (accessToken, refreshToken, profile, done) => {
      try {
        let user = await User.findOne({ googleId: profile.id });
 
        if (!user) {
          user = await User.create({
            googleId: profile.id,
            email: profile.emails[0].value,
            name: profile.displayName,
            picture: profile.photos[0].value,
            refreshToken: refreshToken, // Store refresh token
          });
        } else if (refreshToken) {
          // Update refresh token if new one is provided
          user.refreshToken = refreshToken;
          await user.save();
        }
 
        return done(null, user);
      } catch (error) {
        return done(error, null);
      }
    }
  )
);

Multiple OAuth Providers

You can easily add other providers like Facebook or GitHub:

npm install passport-facebook passport-github2

Configure each strategy similar to Google OAuth and create separate routes for each provider.

Deployment Considerations

Environment Configuration

When deploying to production (Heroku, AWS, Vercel, etc.), update your environment variables:

  1. Set production OAuth credentials
  2. Update callback URLs in Google Cloud Console
  3. Enable HTTPS
  4. Set NODE_ENV=production

Scaling Considerations

For applications expecting high traffic:

  • Use Redis for session storage instead of memory-based sessions
  • Implement rate limiting to prevent abuse
  • Consider using JWT tokens for stateless authentication

Performance Optimization

Session Store with Redis

npm install connect-redis redis
const redis = require('redis');
const RedisStore = require('connect-redis')(session);
const redisClient = redis.createClient();
 
app.use(
  session({
    store: new RedisStore({ client: redisClient }),
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
  })
);

Testing Your OAuth Implementation

Create a test file test/auth.test.js:

const request = require('supertest');
const app = require('../server');
 
describe('OAuth Routes', () => {
  test('GET / returns homepage', async () => {
    const response = await request(app).get('/');
    expect(response.statusCode).toBe(200);
  });
 
  test('GET /profile redirects unauthenticated users', async () => {
    const response = await request(app).get('/profile');
    expect(response.statusCode).toBe(302);
  });
});

Conclusion

Implementing Google OAuth in Node.js doesn't have to be complicated. By following this guide, you've learned how to:

  • Set up Google OAuth credentials
  • Build a complete authentication system with Passport.js
  • Implement database integration for user management
  • Follow security best practices
  • Handle common issues and errors
  • Deploy your application to production

OAuth provides a secure, user-friendly authentication method that can significantly improve your application's user experience. The patterns we've covered here can be extended to support multiple OAuth providers, giving your users even more login options.

For more Node.js tutorials and web development guides, check out ItsEzCode where we break down complex concepts into easy-to-follow tutorials.

Additional Resources

Need Help?

If you run into issues implementing Google OAuth in your Node.js application, feel free to check out more tutorials at ItsEzCode. We're here to help you build better applications!


Did you find this guide helpful? Share it with fellow developers who might benefit from learning about Google OAuth integration in Node.js. Happy coding!

Author

Malik Saqib

I craft short, practical AI & web dev articles. Follow me on LinkedIn.