4

I am using facebook and google oauth2 login using passport js, with this flow

  1. User clicked the login button
  2. Redirects to facebook/google auth page (depending on what login the user chooses)
  3. The auth page redirects back to a callback page (/auth/callback/[provider])
  4. A passport express middleware will catch it to parse some data and then send it to a remote api of myown to sign the user in
  5. The auth remote api will send a response back consisting the user token
  6. A custom express middleware will catch the response to set cookie on the server
  7. the express chain ends by route it to /profile (cookie with token is set on the browser)
  8. /profile will then checks if there is a token, if there is not: it will redirect to /

Doing this flow on facebook login is fine, the user is successfully redirected to /profile, with all of its data and token, the google oauth2 login however seems to be doing the redirect to /profile then setting the token (step #7 then #6), so everytime the user is using google oauth2 login, its always gonna be redirected back to / since by the time it arrives at /profile, it doesnt have the token

here's the code on the above's flow

#./server.js

const express = require('express')
const next = require('next')
const Passport = require('./server/middleware/passport')

const Api = require('./server/api')

const port = parseInt(process.env.PORT, 10)
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app
  .prepare()
  .then(() => {
    const server = express()
    // ... other unrelated things
    server.use(Passport.initialize())

    Api.passport.facebook(server)
    Api.passport.facebookCallback(server)
    Api.passport.google(server)
    Api.passport.googleCallback(server)

    // ... other unrelated things

    server.all('*', (req, res) => handle(req, res))

    server.listen(port, (error) => {
      if (error) throw error

      // ... other unrelated things
    })
  })

#./server/api.js

const Passport = require('middleware/passport')

function setCookie(req, res, next) {
  res.cookie('token', req.user.auth.token, {
    httpOnly: true,
    sameSite: 'strict',
    path: '/',
    secure: process.env.NODE_ENV !== 'development',
  })
  next()
}

function facebook(app) {
  return app.get('/auth/facebook', (req, res, next) => {
    Passport.authenticate('facebook', {
      scope: ['email', 'public_profile']
    })(req, res, next)
  })
}

function facebookCallback(app) {
  return app.get(
    '/auth/callback/facebook',
    Passport.authenticate('facebook', { session: false, failureRedirect: '/' }),
    setCookie,
    (req, res) => {
      res.redirect('/profile')
    },
  )
}

function google(app) {
  return app.get('/auth/google', (req, res, next) => {
    Passport.authenticate('google', {
      scope: [
        'https://www.googleapis.com/auth/userinfo.email ',
        'https://www.googleapis.com/auth/userinfo.profile ',
      ],
      prompt: 'consent',
      authType: 'rerequest',
      accessType: 'offline',
    })(req, res, next)
  })
}

function googleCallback(app) {
  return app.get(
    '/auth/callback/google',
    Passport.authenticate('google', { failureRedirect: '/', session: false }),
    setCookie,
    (req, res) => {
      res.redirect('/profile')
    },
  )
}

module.exports = {
  passport: {
    facebook,
    facebookCallback,
    google,
    googleCallback,
  }
}

#./server/middleware/passport.js

const axios = require('axios')
const passport = require('passport')
const GoogleStrategy = require('passport-google-oauth20').Strategy
const FacebookStrategy = require('passport-facebook').Strategy


passport.serializeUser((user, done) => {
  done(null, user)
})

passport.deserializeUser((obj, done) => {
  done(null, obj)
})

function verifyCallback(req, ... , done) {
  process.nextTick(async () => {
    try {
      const options = {
        baseURL: baseUrl, // My remote api url
        method: 'POST',
        url: '/auth/signin',
        headers: {
          'Content-Type': 'application/json',
        },
        data: JSON.stringify({
          // email, fullname, etc
        }),
      }
      const response = await axios(options)

      return done(null, response.data)
    } catch (error) {
      const { response } = error
      return done(JSON.stringify(response.data, null, 2), null)
    }
  })
}

passport.use(new GoogleStrategy({
  clientID: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  callbackURL: callbackURLGoogle,
  passReqToCallback: true,
}, verifyCallback))

passport.use(new FacebookStrategy({
  clientID: process.env.FACEBOOK_CLIENT_ID,
  clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
  callbackURL: callbackURLFacebook,
  enableProof: true,
  profileFields: ['id', 'name', 'email', 'picture.type(large)'],
  passReqToCallback: true,
}, verifyCallback))

module.exports = passport

I console.log() things, just to figure out if it falls to the correct sequence of flow, the console doesn't seem to log anything suspicious, is there's something i am missing here?

PS: i am also using next js with custom server

littlechad
  • 1,202
  • 17
  • 48
  • Have you tried using async and await with the function you're sending the request from? It will tell the function to wait for the response. – Package.JSON Mar 22 '21 at 05:27
  • which function? the one that sets the cookie? – littlechad Mar 23 '21 at 05:24
  • Don't you have some issue in logging in to Google? Then your Google strategy fails over to `'/'`. (See `Passport.authenticate('google', { failureRedirect: '/', session: false }),` Have you all the settings in Google Developer Console? And what is your callbackURLGoogle address? – Fide Mar 25 '21 at 22:09
  • @Fide no, i dont have issue logging in to google, i can confirm it on `verifyCallback` function above, and it has also reached my remote auth api with a success response, the reason why it went to `/`, is not because of the `failureRedirect` but because on `/account`, i checked if there is any token cookie or not, if not redirect to `/` – littlechad Mar 26 '21 at 10:35
  • Can you log `req.user.auth.token` in setCookie? Passport documentation: `passport.user(new GoogleStrategy(), --> function(accessToken, refreshToken, profile, done) <--` The verifyCallback receives according to Passport documentation `(accessToken, refreshToken, profile, done)` - you're parsing request. – Fide Mar 26 '21 at 11:40
  • the `req.user` on setcookie, has the correct object, which is the one returned from my remote api, and as for the `verifyCallback`, i used `...` just to simplify, where in the code its actually `(req, accessToken, refreshToken, profile, done)`, i have `req` available as the first argument, because i am using the `passReqToCallback: true` option – littlechad Mar 27 '21 at 10:30

1 Answers1

1

I was facing the same problem and was able to send cookies by using custom callback.

router.get('/google/callback', (req, res) => {
  passport.authenticate('google', {session: false, failureRedirect:'/auth/google/failure'},
  async(err, user) => {

    // You can send cookies and data in response here.

  })(req, res)
})

Please refer custom callback section in documentation for explanation.

Rahul Jain
  • 107
  • 2
  • 8