Google authentication with passport in node.js. How to generate my own jwt token after successful login?

154 views Asked by At

Here are all the resources I've based to build my solution/strategy for this:

https://medium.com/free-code-camp/how-to-set-up-twitter-oauth-using-passport-js-and-reactjs-9ffa6f49ef0 https://medium.com/@techsuneel99/jwt-authentication-in-nodejs-refresh-jwt-with-cookie-based-token-37348ff685bf What's the best way to add social login (Sign in with Google) to existing email/password app and database? What is the best practice to use Oauth2, React, Node.js and Passport.js to authenticate user with Google sign on button?

=================================

I have a FE React app running in localhost:3000 and my backend in Node Express in localhost:5000

I'm trying to implement Google login using passport library. Just the simple Google login works fine. However I'm a bit confused on what to do next.Because I also have a login/signup using email/password, in case user logs in with google I want to see if there's already an user with that email in my database. If so I then make the association to that user and get their data from my db instead of using their google profile. I guess I also want to generate my own JWT token to handle their authentication in my system. Then I would return that token to my FE to use it as Bearer token for authentication. In case there's no user,the FE would redirect the user to the signup page

So, here is the flow I have in mind and how I'm implementing it:

React FE (localhost:3000) calls BE on localhost:5000/auth/google My GoogleLoginButton component in the FE:

import type React from 'react'

export const GoogleLoginButton: React.FC = () => {
  const handleGoogleLogin = (ev) => {
    ev.preventDefault()
    window.open('http://localhost:5000/auth/google', '_self')
  }

  return (
    <a href="#" onClick={handleGoogleLogin}>
      Login with Google
    </a>
  )
}

and the route definition in the BE:

app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }));

BE calls google service, user logs in and I get a user google profile, access token and refresh token (not sure what I do with these tokens, since I'll generate my own jwt -I guess nothing? )

Here is the relevant implementation for Google Strategy with passport:

passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID || 'fakeId',
      clientSecret: process.env.GOOGLE_CLIENT_SECRET || 'fakeSecret',
      callbackURL: '/auth/google/callback',
      passReqToCallback: true
    },
    async (_req, _accessToken, _refreshToken, profile, done) => {


      let existingUser = await oauthTokenService.findUserByEmail(profile.emails[0].value, 'mybrand');

      if (!existingUser) {
        console.log("We will send nothing for now as user will have to register on FE")
        return done(null, undefined);
      }

      return done(null, existingUser);
    },
  ),
);

In here I look for an existing user with that email account in my database, in case the user already has registered using the password/username flow (which was already done in the system)

And here I have my jwt strategy using passport:

passport.use(
  "jwt_strategy",
  new JwtStrategy(
    {
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'myFakeSecretKey',
    },
    async (payload, done) => {
      const user = await User.findByPk(payload.id)
        if (user) {
          return done(null, user);
        } else {
          return done(null, false);
        }
      }
  )
);

The idea is that I want to return that token to my frontend and use it to check if the user is logged in or not.

Finally here is my /auth/google/callback handler:

app.get('/auth/google/callback',
  passport.authenticate("google", {
    failureRedirect: "/failedLogin",
    session:false
  }),
  function (req, res) {
    const token = jwt.sign({user:{"email":req.user.email},id: req.user.id, clientId:'myFakeClientId'}, 'fakeSecretKey');
    res.send( {token })
  }
);

and an endpoint that returns my user data only if I pass a valid Bearer token in the authorization header:

app.get('/getDetails', passport.authenticate('jwt_strategy', { session: false }), (req, res)=>{
  console.log("Request on getDetails:")
  console.log(req.user);
  return res.status(200).json({
    message: 'User authenticated',
      user: req.user,
  });
});

If I do it like I did above, I get a token back successfully and I can test on Insomnia/Postman that a request to my user details works correctly. The problem is that in my FE after the whole flow I'm sent to localhost:5000/auth/google/callback which of course just displays a json object with my token.

I tried to add successRedirect so it stays like this now:

app.get('/auth/google/callback',
  passport.authenticate("google", {
    failureRedirect: "/failedLogin",
    successRedirect: "http://localhost:3000",
    session:false
  }),
  function (req, res) {
    const token = jwt.sign({user:{"email":req.user.email},id: req.user.id, clientId:'myFakeClientId'}, 'fakeSecretKey');
    console.log("Token? ")
    console.log(token)
    res.send( {token })
  }
);

which it does bring me to localhost:3000 but then I don't have the token generated because I don't think that function gets executed as the redirect already happened at that point.

I need to be able to return token to my FE, but of course I don't want the browser to go to localhost:5000/auth/google/callback. I want to stay where I am. How can I achieve that? Also, since this is all kinda new to me, feel free to suggest a better flow if there's one

0

There are 0 answers