Google OAuth

The Google OAuth component provides a secure and standardized way to integrate Google authentication into your ServerCN Express applications using the official google-auth-library.

It handles the complete OAuth 2.0 flow including authorization URL generation, token exchange, user information retrieval, and token refresh.

Features

  • Complete OAuth 2.0 flow - Authorization, token exchange, and user info retrieval
  • Secure by default - CSRF protection with state parameter
  • Token management - Access token, refresh token, and ID token verification
  • Express integration - Ready-to-use route handlers
  • Type-safe - Full TypeScript support
  • Flexible scopes - Customizable OAuth scopes

Installation Guide

This component requires additional ServerCN components.

👉 Note: You do not need to install any servercn dependencies manually. Installing this component will automatically install all required servercn dependencies. Manual installation is optional if you prefer to manage dependencies yourself.

  • HTTP Status Codes:
npx servercn add http-status-codes

Documentation: HTTP Status Codes

  • Api Response Handler:
npx servercn add response-formatter

Documentation: Api Response Handler

  • Api Error Handler:
npx servercn add error-handler

Documentation: Api Error Handler

  • Async Handler:
npx servercn add async-handler

Documentation: Async Handler

npx servercn add google-oauth

Prerequisites

  1. Go to the Google Cloud Console
  2. Create a new project or select an existing one
  3. Enable the Google+ API (or Google Identity API)
  4. Go to CredentialsCreate CredentialsOAuth client ID
  5. Configure the OAuth consent screen:
    • Choose External (for testing) or Internal (for Google Workspace)
    • Fill in the required information
  6. Create OAuth 2.0 Client ID:
    • Application type: Web application
    • Authorized redirect URIs: Add your callback URL (e.g., http://localhost:8000/api/auth/google/callback)
  7. Copy the Client ID and Client Secret

Add the following to your .env file:

GOOGLE_CLIENT_ID="your-google-client-id.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
GOOGLE_REDIRECT_URI="http://localhost:8000/api/auth/google/callback"

Ensure the following configuration are defined:

src/configs/env.ts ||

src/shared/configs/env.ts
interface Config {
  PORT: number;
  NODE_ENV: string;
  LOG_LEVEL: string;
 
  GOOGLE_CLIENT_ID: string;
  GOOGLE_CLIENT_SECRET: string;
  GOOGLE_REDIRECT_URI: string;
}
 
const env: Config = {
  PORT: Number(process.env.PORT) || 3000,
  NODE_ENV: process.env.NODE_ENV || "development",
  LOG_LEVEL: process.env.LOG_LEVEL || "info",
 
  GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID!,
  GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET!,
  GOOGLE_REDIRECT_URI: process.env.GOOGLE_REDIRECT_URI!
};
 
export default env;

Basic Implementation

Place the Google OAuth utilities in src/utils/google-oauth.ts:

import { OAuth2Client } from "google-auth-library";
import env from "../configs/env";
 
const oauth2Client = new OAuth2Client(
  env.GOOGLE_CLIENT_ID,
  env.GOOGLE_CLIENT_SECRET,
  env.GOOGLE_REDIRECT_URI
);
 
export function getGoogleAuthUrl(
  scopes: string[] = [
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/userinfo.profile"
  ]
) {
  const state = Math.random().toString(36).substring(2, 15);
  const authUrl = oauth2Client.generateAuthUrl({
    access_type: "offline",
    scope: scopes,
    state,
    prompt: "consent"
  });
 
  return { authUrl, state };
}
 
export async function getGoogleTokens(code: string) {
  const { tokens } = await oauth2Client.getToken(code);
  oauth2Client.setCredentials(tokens);
  return tokens;
}
 
export async function getGoogleUserInfo(accessToken: string) {
  const userInfoResponse = await fetch(
    "https://www.googleapis.com/oauth2/v2/userinfo",
    {
      headers: {
        Authorization: `Bearer ${accessToken}`
      }
    }
  );
 
  if (!userInfoResponse.ok) {
    throw new Error("Failed to fetch user info from Google");
  }
 
  return userInfoResponse.json();
}

For feature-based architecture, place helpers in src/modules/auth/google-oauth.helpers.ts:

import { OAuth2Client } from "google-auth-library";
 
const oauth2Client = new OAuth2Client(
  process.env.GOOGLE_CLIENT_ID!,
  process.env.GOOGLE_CLIENT_SECRET!,
  process.env.GOOGLE_REDIRECT_URI!
);
 
// Same functions as MVC structure

Usage Examples

Create a route to start the Google OAuth flow:

import { Router } from "express";
import { getGoogleAuthUrl } from "../utils/google-oauth";
 
const router = Router();
 
router.get("/auth/google", async (req, res) => {
  const { authUrl, state } = getGoogleAuthUrl();
 
  // Store state in session/Redis for CSRF protection
  req.session.oauthState = state;
 
  // Redirect user to Google
  res.redirect(authUrl);
});

Handle the callback from Google:

router.get("/auth/google/callback", async (req, res) => {
  const { code, state } = req.query;
 
  // Verify state to prevent CSRF
  if (state !== req.session.oauthState) {
    return res.status(400).json({ error: "Invalid state parameter" });
  }
 
  try {
    // Exchange code for tokens
    const tokens = await getGoogleTokens(code as string);
 
    // Get user information
    const userInfo = await getGoogleUserInfo(tokens.access_token!);
 
    // Create or update user in your database
    // Generate your own JWT tokens
    // Return tokens to client
 
    res.json({ user: userInfo, tokens });
  } catch (error) {
    res.status(500).json({ error: "Authentication failed" });
  }
});

If using Google Sign-In with ID tokens:

import { verifyGoogleIdToken } from "../utils/google-oauth";
 
router.post("/auth/google/verify", async (req, res) => {
  const { idToken } = req.body;
 
  try {
    const payload = await verifyGoogleIdToken(idToken);
    // payload contains user information
    res.json({ user: payload });
  } catch (error) {
    res.status(401).json({ error: "Invalid token" });
  }
});

Refresh an expired access token:

import { refreshGoogleToken } from "../utils/google-oauth";
 
router.post("/auth/google/refresh", async (req, res) => {
  const { refreshToken } = req.body;
 
  try {
    const credentials = await refreshGoogleToken(refreshToken);
    res.json({ tokens: credentials });
  } catch (error) {
    res.status(401).json({ error: "Token refresh failed" });
  }
});

Integration with JWT Authentication

Combine Google OAuth with your JWT authentication:

import { getGoogleTokens, getGoogleUserInfo } from "../utils/google-oauth";
import { generateAccessToken, generateRefreshToken } from "../utils/jwt";
 
router.get("/auth/google/callback", async (req, res) => {
  const { code } = req.query;
 
  try {
    // Get Google tokens
    const googleTokens = await getGoogleTokens(code as string);
    const userInfo = await getGoogleUserInfo(googleTokens.access_token!);
 
    // Find or create user in database
    let user = await User.findOne({ email: userInfo.email });
    if (!user) {
      user = await User.create({
        email: userInfo.email,
        name: userInfo.name,
        picture: userInfo.picture,
        provider: "google",
        providerId: userInfo.id
      });
    }
 
    // Generate your own JWT tokens
    const accessToken = generateAccessToken({ _id: user._id });
    const refreshToken = generateRefreshToken(user._id);
 
    res.json({
      accessToken,
      refreshToken,
      user: {
        id: user._id,
        email: user.email,
        name: user.name
      }
    });
  } catch (error) {
    res.status(500).json({ error: "Authentication failed" });
  }
});

Available OAuth Scopes

Common Google OAuth scopes you can request:

  • https://www.googleapis.com/auth/userinfo.email - User's email address
  • https://www.googleapis.com/auth/userinfo.profile - User's basic profile info
  • https://www.googleapis.com/auth/user.birthday.read - User's birthday
  • https://www.googleapis.com/auth/user.phonenumbers.read - User's phone numbers
  • https://www.googleapis.com/auth/calendar.readonly - Read-only access to user's calendar

Security Best Practices

  1. Always verify the state parameter to prevent CSRF attacks
  2. Store refresh tokens securely in your database (encrypted)
  3. Use HTTPS in production for redirect URIs
  4. Validate redirect URIs match your configured ones
  5. Set appropriate token expiration times
  6. Handle token refresh automatically when access tokens expire

Error Handling

The component throws errors that can be caught and handled:

try {
  const tokens = await getGoogleTokens(code);
} catch (error) {
  if (error instanceof Error) {
    // Handle specific Google OAuth errors
    console.error("OAuth error:", error.message);
  }
}

Common Issues

Ensure your redirect URI in .env exactly matches the one configured in Google Cloud Console.

This usually means:

  • The authorization code has expired (codes expire after a few minutes)
  • The code has already been used
  • The redirect URI doesn't match

The user denied permission. Handle this gracefully in your UI.

File & Folder Structure

Select a file to view its contents

Installation

npx servercn add google-oauth