2

I have successfully implemented authentication using gmail account in my app.
The problem is when the user signs-in again, the browser automatically picks the previous account which breaks the flow as I will explain below. p

Based on my research, adding prompt: "select_account" here should have solved the issue. But, it had no effect.

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

Here's how automatically picking the user account that was previously used to sign-in breaks the sign-in if the user tries to sign-in again.
This is how the sign-in works:

  1. STEP 1:

This endpoint is called from the frontend:

router.get(
  "/auth/google",
  passport.authenticate("google", {
    scope: [
      "email",
      "profile",
    ],
    prompt: "select_account",
  })
);
  1. STEP 2:

After, the user picks an account, he is redirected to this callback endpoint:

router.get(
  "/auth/google/callback",
  passport.authenticate("google", {
    failureRedirect: baseFrontendUrl,
    session: false,
  }),
  function (req, res) {
    User.findOne({ _id: req.user._id })
      .then((user) => {
        const payload = {
          id: req.user._id,
        };
        console.log(" ~ file: users.js:178 ~ .then ~ payload", payload);
        jwt.sign(
          payload,
          keys.SecretKey,
          { expiresIn: 3600 * 24 * 356 },
          (error, token) => {
            if (error) {
              res.status(400).json({
                message: "Error logging in user.",
              });
            } else {
              const redirect_url = `${baseFrontendUrl}/OAuthRedirecting?token=${token}`;

              res.redirect(redirect_url);
            }
          }
        );
      })
      .catch((error) => {
        res.status(500).json({
          message: "An error occured authenticating user using google.",
        });
      });
  }
);

The problem is that if the user does not pick an account, he does not get redirected to that endpoint. So the second sign-in fails.

A solution to this could be to force the user to pick an account every time he signs-in but I couldn't find a way to do this.


This is how the google passport strategy is implemented:

passport.use(
  new GoogleStrategy(googe_passport_config, function (
    request,
    accessToken,
    refreshToken,
    google_profile,
    done
  ) {
    let name = !!google_profile._json.given_name
      ? google_profile.given_name
      : "Name";
    let surname = !!google_profile._json.family_name
      ? google_profile.family_name
      : "Surname";
    let email = !!google_profile._json.email ? google_profile.email : "";

    User.findOne({ email: google_profile._json.email })
      .then((user) => {
      
        if (!!user) {
          return done(null, user);
        } else {
          userServices
            .registerUserThroughGoogleAuth(
              name,
              surname,
              email,
              google_profile.id
            )
            .then((created_user) => {
              if (!!created_user) {
                return done(null, created_user);
              }
            })
            .catch((error) => {
              const error_to_be_returned = new Error("Error creating user");
              return done(error_to_be_returned, null);
            });
        }
      })
      .catch((error) => {
        const error_to_be_returned = new Error("Error finding user");
        return done(error_to_be_returned, null);
      });
  })
);

I added some console logs there and nothing gets logged the second time the user tries to sign-in. So it's not even getting called.

AG_HIHI
  • 1,705
  • 5
  • 27
  • 69

2 Answers2

2

You may try to add a random parameter to the authentication request URL to force user to select an account every time they sign in. As your prompt: "select_account" is not working might caused by the browser cached the user's previous login credentials and automatically signs them in without showing the select account prompt.

router.get(
  "/auth/google",
  (req, res, next) => {
    req.session.google_oauth2_state = Math.random().toString(36).substring(2);
    next();
  },
  passport.authenticate("google", {
    scope: ["email", "profile"],
    prompt: "select_account",
    state: true,
  })
);

The random parameter will cause the browser to treat the request as a new URL, even if the user has previously signed in to your app with the same account. state: true option is set to include the state parameter in the authentication request. This is required for Google's OAuth2 protocol to prevent CSRF attacks

Another workaround is to combine select_account and consent to force the account selection page to show. Your select_account is not working could be that the browser may have cached the user's previous login credentials and automatically signs them in without showing the select account prompt. Adding the consent would force the user to select their account every time they sign in and will also ask for the user's permission again.

router.get(
  "/auth/google",
  passport.authenticate("google", {
    scope: [
      "email",
      "profile",
    ],
    prompt: "select_account consent",
  })
);
Joshua Ooi
  • 1,139
  • 7
  • 18
  • 1
    I tried the second approach. And I can log out and login many times and it works just fine :) It still doesn't force me to pick an account but I think this is cause by the fact that each chrome session has one single authenticated user associated with it. Thank you so much! – AG_HIHI Feb 28 '23 at 07:46
  • I tried it on my smartphone where the browser isn't associated with a single gmail user I think and I have multiple gmail accounts setup on my phone so it should have forced me to pick an account but it didn't. But, still, this is better than the whole second sign-in failing. – AG_HIHI Feb 28 '23 at 07:49
  • I also noticed when I open a new tab, or reopen the browser, the user is logged-out of the app. For some reason, the jwt token gets deleted when I login using google auth. When I login, using email and password, that doesn't happen. This only started happening after I used that second approach. I have no idea why. I'll debug it. Just typing this in case it happens to someone else. – AG_HIHI Feb 28 '23 at 08:15
  • Trying the first approach throws this error: TypeError: Cannot set property 'google_oauth2_state' of undefined – AG_HIHI Feb 28 '23 at 08:44
0

you should log the user out from google authentication after successful login to your application, that way the Google sign in will request from user to select which account to use again whenever they click the Google login to sign in

Ismi Ammar
  • 166
  • 1
  • 11
  • Not sure how I can do that – AG_HIHI Feb 28 '23 at 07:11
  • Sorry. The suggestion only work for mobile app. The above comments similar to the answer provided here. https://stackoverflow.com/questions/14384354/force-google-account-chooser?noredirect=1&lq=1 – Ismi Ammar Feb 28 '23 at 14:10