DEV Community

Ayush Pandey
Ayush Pandey

Posted on

PassportJs-Backend Explanation

Passport Setup with Google Strategy
We are using Passport.js with the Google OAuth 2.0 strategy to allow users to log in with their Google account. In the main passport configuration file, we set up a strategy with the client ID, client secret, and callback URL provided by Google. When Google redirects back with the user profile, the verify callback is executed. In this callback, the system first checks if a user with the given Google ID already exists in the database. If the user does not exist, a new record is created with details such as full name, email, and profile picture, and a welcome email is sent using Nodemailer. Whether the user is new or already exists, the user object is returned to Passport, which makes it available in the request object (req.user).

import { Request } from "express";
import passport from "passport";
import { Strategy, Profile, VerifyCallback } from "passport-google-oauth20";
import { User } from "../models/userModel";

passport.use(
  new Strategy(
    {
      clientID: process.env.CLIENT_ID || "",
      clientSecret: process.env.CLIENT_SECRET || "",
      callbackURL:process.env.CALLBACK_URL_PASSPORT,
      passReqToCallback: true,
    },
    async (
      req: Request,
      accessToken: string,
      refreshToken: string,
      profile: Profile,
      done: VerifyCallback
    ) => {
      try {
        let user = await User.findOne({
          googleId: profile.id,
        });

        if (!user) {
          user = await User.create({
            googleId: profile.id,
            fullName: profile.displayName,
            email: profile.emails?.[0]?.value,
            profilePicture: profile.photos?.[0]?.value, 
          });
        }
        return done(null, user);
      } catch (error) {
        return done(error);
      }
    }
  )
);
Enter fullscreen mode Exit fullscreen mode

Generating JWT Tokens for Authenticated Users
Once Passport successfully authenticates a user, we still need our own application-specific authentication system. For that, we generate two JWT tokens: an access token (short-lived, for API requests) and a refresh token (longer-lived, used to get new access tokens). These are generated in the googleCallback method of the AuthController. After validation, the tokens are stored securely in HTTP-only cookies so they cannot be accessed by JavaScript in the browser. For development convenience, the tokens are also appended as query parameters when redirecting the user to the frontend success page. This way, the frontend can read them directly if needed, while the cookies are already set for secure API communication.


export class AuthController {
  public googleCallback = asyncHandler(
    async (req: Request, res: Response): Promise<void> => {
      const user = req.user as Iuser;

      if (!user) {
        throw new ApiError(403, "Permission Denied");
      }

      const refreshToken = user.generateRefreshToken();
      const accessToken = user.generateAccessToken();

      res.cookie("accessToken", accessToken, {
        httpOnly: true,
        secure: true,
        path: "/",
        sameSite: "none"
      });

      res.cookie("refreshToken", refreshToken, {
        httpOnly: true,
        secure: true,
        sameSite: "none",
        path: "/"
      });

      res.redirect(
        `http://localhost:5173/oauth-success?accessToken=${accessToken}&refreshToken=${refreshToken}`
      );
    }
  );
}

Enter fullscreen mode Exit fullscreen mode

Route Setup and Usage
To make this work end-to-end, we defined two routes in googleRoutes.ts. The first route, GET /google, starts the Google login flow by calling passport.authenticate("google", { scope: ["profile", "email"] }), which redirects the user to Google’s login page. The second route, GET /google/callback, is the callback endpoint that Google will hit after the user logs in. Here, Passport runs the strategy again, attaches the user to req.user, and then passes control to our controller method googleCallback. This controller handles token generation, sets the cookies, and finally redirects the user to the frontend application (http://localhost:5173/oauth-success) with the tokens. Together, these routes ensure the entire login flow works smoothly from frontend to Google to backend and back to frontend.

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

router.get(
  "/google/callback",
  passport.authenticate("google", {
    session: false,
    failureRedirect: "/login",
  }),
googleCallback
);


Enter fullscreen mode Exit fullscreen mode

Top comments (0)