DEV Community

Cover image for Google Login in Express with PassportJS & JWT
NHero
NHero

Posted on • Originally published at nhero.me

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
Enter fullscreen mode Exit fullscreen mode

Install Required Packages

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

npm install -D @types/passport-google-oauth20
Enter fullscreen mode Exit fullscreen mode

Create Google OAuth Credentials

Go to Google Cloud Console
{/*

https://console.cloud.google.com/
Enter fullscreen mode Exit fullscreen mode


*/}

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
Enter fullscreen mode Exit fullscreen mode

Important User Model Changes

Your user model MUST contain these fields:

refreshToken: {
  type: String,
},

googleId: {
  type: String,
},
Enter fullscreen mode Exit fullscreen mode

If you already support password login, make password optional:

password: {
  type: String,
  required: false,
},
Enter fullscreen mode Exit fullscreen mode

Also add checks wherever password login is used:

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

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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,
    });
};
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

Initialize Passport

Inside app.ts

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

app.use(passport.initialize());
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

Production Tips

Use secure cookies in production:

secure: true,
httpOnly: true,
sameSite: "strict",
Enter fullscreen mode Exit fullscreen mode

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 🚀

Top comments (0)