Google Login in Express with PassportJS & JWT

In this blog, we will implement Google Authentication in an Express.js app using:

  • TypeScript
  • Express.js
  • Mongoose
  • Passport.js
  • JWT Authentication

Project Structure

└── src/
    ├── config/
    ├── controllers/
    ├── middlewares/
    ├── models/
    ├── routes/
    ├── utils/
    ├── app.ts
    └── index.ts
├── .env
├── package.json
└── tsconfig.json

Install Required Packages

npm install passport passport-google-oauth20
npm install jsonwebtoken cookie-parser bcrypt mongoose

npm install -D @types/passport-google-oauth20

Create Google OAuth Credentials

Go to Google Cloud Console
{/*

https://console.cloud.google.com/

*/}

Steps

1. Create a new project

create a new project

2. Configure OAuth Consent Screen

Configure OAuth Consent Screen

3. Create OAuth 2.0 Client ID

go to clients

create oauth2.0 client

4. Configure OAuth Client

create oauth2.0 client

5. Copy Client ID and Client Secret

copy credentials

Environment Variables

Create a .env file:

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=http://localhost:3000/api/v1/auth/google/callback

Important User Model Changes

Your user model MUST contain these fields:

refreshToken: {
  type: String,
},

googleId: {
  type: String,
},

If you already support password login, make password optional:

password: {
  type: String,
  required: false,
},

Also add checks wherever password login is used:

if (!user.password) {
  throw new Error("Password login not available for this account");
}

Configure PassportJS

Create: src/config/passport.ts

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("No email found"));
        }

        let user = await User.findOne({
          $or: [{ googleId: profile.id }, { email }],
        });

        if (user && !user.googleId) {
          user.googleId = profile.id;
          await user.save();
        }

        const username = await generateUsername(profile.displayName);

        if (!user) {
          user = await User.create({
            username,
            fullName: profile.displayName,
            email,
            googleId: profile.id,
            avatarUrl: profile.photos?.[0]?.value || "",
          });
        }

        user = await User.findById(user._id).select(
          "-password -refreshToken -googleId"
        );

        if (!user) {
          return done(new Error("User not found after creation"));
        }

        return done(null, user);
      } catch (error) {
        return done(error as Error);
      }
    }
  )
);

export default passport;

Username Generator

Create: src/utils/usernameGen.ts

Your logic should:

  • Convert full name into username
  • Add numbers if username already exists
  • Ensure uniqueness

Example:

john
john12
john123

Google Auth Controller

src/controllers/auth.controller.ts

export const googleAuth = async (req: Request, res: Response) => {
  const user = req.user!;

  const { accessToken, refreshToken } =
    await generateTokens(user._id);

  return res
    .status(200)
    .cookie("accessToken", accessToken, cookieOptions)
    .cookie("refreshToken", refreshToken, cookieOptions)
    .json({
      success: true,
      user,
    });
};

Google Auth Routes

src/routes/auth.route.ts

router.route("/google").get(
  passport.authenticate("google", {
    scope: ["profile", "email"],
    session: false,
  })
);

router.route("/google/callback").get(
  passport.authenticate("google", {
    failureRedirect: "/login",
    session: false,
    failureMessage: "Failed to login with Google",
  }),
  googleAuth
);

Initialize Passport

Inside app.ts

import passport from "./config/passport.js";

app.use(passport.initialize());

Authentication Flow

  1. User visits: /api/v1/auth/google
  2. Google login page opens
  3. User authenticates
  4. Google redirects back to callback URL
  5. User gets JWT tokens
  6. Cookies are set

Testing

Open: http://localhost:3000/api/v1/auth/google

If configured correctly:

  • Google login opens
  • User account gets created
  • JWT cookies are generated

Common Issues

Invalid Redirect URI

Ensure callback URL matches exactly in:

  • Google Console
  • .env
  • Passport Strategy

Missing Email

Make sure scope contains:

scope: ["profile", "email"]

Production Tips

Use secure cookies in production:

secure: true,
httpOnly: true,
sameSite: "strict",

Final Thoughts

You now have Google Authentication working with:

  • Express.js
  • TypeScript
  • Mongoose
  • Passport.js
  • JWT

You can further extend this with:

  • GitHub OAuth
  • Discord OAuth
  • Role-based auth
  • Email verification

Happy coding 🚀