Search code examples
node.jsauthenticationpassport.jsgraphql-jspassport-google-oauth

Trying to use Google Oauth2 with Passportjs in Graphql-Yoga Server


I thought I followed all of the docs correctly to implement this, however, I am getting a TokenError: Bad Request that has the error message invalid_grant.

My server is super simple:

require('dotenv').config();
import createServer from './createServer';

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

const server = createServer();

passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: 'http://localhost:4000/auth/callback',
    },
    (accessToken, refreshToken, profile, cb) => {
      console.log(accessToken);
      console.log(refreshToken);
      console.log(profile);

      cb(null, profile);
    },
  ),
);

server.express.use(
  '/auth',
  passport.authenticate('google', {
    scope: ['email', 'profile'],
    session: false,
  }),
);

server.express.use(
  '/auth/callback',
  passport.authenticate('google', {
    successRedirect: 'http://localhost:3000/authenticate',
    failureRedirect: 'http://localhost:3000/authenticate',
  }),
);

server.start(
  {
    cors: {
      credentials: true,
      origin: 'http://localhost:3000',
    },
  },
  () => console.log('Server is running on http://localhost:4000'),
);

Is this a problem with the way that I have setup Google in the cloud platform? I can't figure out where I went wrong. My callback is correctly setup. I'm not sure where else to look for a mistake?

Another thing that is confusing is that the GoogleStategy is console logging the user profile and the access token that is returned. I am guessing that the error comes when the callback route tries to verify the code from the URL. Can anyone point me in a direction to look to better troubleshoot this? Thanks in advance.


Solution

  • I found a solution that works for me, but I am still unclear if it is "best-practice" or not. I would love someone with more GraphQL experience to chime in.

    I followed the directions in the docs to authenticate in the front-end: https://developers.google.com/identity/sign-in/web/backend-auth

    Then I wrote a query called isAuthenticated on the back-end that I can use to verify the token.

    async isAuthenticated(_: any, { token }) {
        if(!token) {
          return null;
        }
    
        const ticket = await client.verifyIdToken({
          idToken: token,
          audience: process.env.GOOGLE_CLIENT_ID,
        });
    
        return payload;
      },
    

    I use a React component to check the token in localStorage before rendering any protected routes. This is working for me.