View as Json

Google OAuth (Passport)

The Google OAuth component provides a secure and standardized way to integrate Google authentication into your Servercn Express applications using the official passport, passport-google-oauth20.

Official Docs


Installation Guide

npx servercn-cli add oauth

You will be prompted to select a file upload provider:

? Select OAuth provider:  » - Use arrow-keys. Return to submit.
>   Google
    GitHub
    Google + GitHub

The CLI will then automatically configure the component based on your selected provider.


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 JavaScript origins: Add your origin URL (e.g., http://localhost:9000)
    • Authorized redirect URIs: Add your callback URL (e.g., http://localhost:9000/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:9000/api/auth/google/callback"
# replace all with your values

Ensure the following configuration are defined:

src/configs/env.ts
import "dotenv-flow/config";
 
interface Config {
  PORT: number;
  NODE_ENV: string;
  LOG_LEVEL: string;
  CORS_ORIGIN: 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",
  CORS_ORIGIN: process.env.CORS_ORIGIN || "*",
 
  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

src/configs/passport.ts
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import env from "./env";
 
const clientId = env.GOOGLE_CLIENT_ID;
const clientSecret = env.GOOGLE_CLIENT_SECRET;
const redirectUri = env.GOOGLE_REDIRECT_URI;
 
passport.use(
  new GoogleStrategy(
    {
      clientID: clientId,
      clientSecret,
      callbackURL: redirectUri
    },
    function (accessToken, refreshToken, profile, cb) {
      return cb(null, profile);
    }
  )
);

src/controllers/google-oauth.controller.ts
import { NextFunction, Request, Response } from "express";
import { Profile } from "passport-google-oauth20";
 
//? import servercn components
import { ApiResponse } from "../utils/api-response";
import { AsyncHandler } from "../utils/async-handler";
import { ApiError } from "../utils/api-error";
 
//? login with google
export const googleOAuth = AsyncHandler(
  async (req: Request, res: Response, next: NextFunction) => {
    const data = req.user as Profile | undefined;
    const user = data?._json;
 
    if (!user || !data) {
      return next(ApiError.unauthorized("Authenticated failed!"));
    }
 
    const userInfo = {
      provider: data?.provider,
      providerId: data.id,
      name: data.displayName,
      email: data?.emails && data?.emails[0]?.value,
      isEmailVerified: data?.emails && data?.emails[0]?.verified,
      avatar: data.profileUrl || (data.photos && data.photos[0].value)
    };
 
    const userInfo2 = {
      provider: data?.provider,
      providerId: user.sub,
      name: user.name,
      email: user.email,
      isEmailVerified: user.email_verified,
      avatar: user.picture
    };
 
    //? save the data into your databases
 
    ApiResponse.ok(res, "Auth Successfull", {
      userInfo,
      userInfo2
    });
  }
);

src/routes/google-oauth.routes.ts
import { Router } from "express";
import passport from "passport";
 
import { googleOAuth } from "../controllers/google-oauth.controller";
 
const router = Router();
 
router.get(
  "/google",
  passport.authenticate("google", {
    scope: ["email", "profile", "openid"],
    prompt: "consent"
  })
);
 
router.get(
  "/google/callback",
  passport.authenticate("google", {
    failureRedirect: "/login", //? redirect route if authenticated is failed
    session: false
  }),
  googleOAuth
);
 
export default router;

src/app.ts
import express, { Express, Request, Response } from "express";
 
import { notFoundHandler } from "./middlewares/not-found-handler";
import { errorHandler } from "./middlewares/error-handler";
 
import AuthRoutes from "./routes/google-oauth.routes";
 
import "./configs/passport";
 
const app: Express = express();
 
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
 
// Routes
app.use("/api/auth", AuthRoutes);
 
// Not found handler (should be after routes)
app.use(notFoundHandler);
 
// Global error handler (should be last)
app.use(errorHandler);
 
export default app;

Success Response

{
  "success": true,
  "message": "Auth Successfull",
  "statusCode": 200,
  "data": {
    "userInfo": {
      "provider": "google",
      "providerId": "1163181840105620881",
      "name": "Akkal Dhami",
      "email": "dhamiakkal21@gmail.com",
      "isEmailVerified": true,
      "avatar": "https://lh3.googleusercontent.com/..."
    }
  }
}

This response is formated by ApiResponse component.

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.

google
github
facebook
google-github
github-facebook
google-facebook
google-facebook-github

File & Folder Structure

Loading files...

Installation

npx servercn-cli add oauth