Boris K
Boris K

Reputation: 3580

Extending an OAuth2 Google Passport.js strategy to use JWT

I've got an email and password strategy set up with JWT and would like to extend it with the Google OAuth set up, rather than using a cookie.

Right now, while the two Google authentication routes work fine, a user is being created in my Users collection and I'm being sent back to the callback route, I'm getting undefined for user when I try to go to the /api/current_user route.

Here's my code: any ideas?

index.js (entry point)

const express = require('express');
const http = require('http');
const bodyParser = require('body-parser');
const morgan = require('morgan');
const app = express();
const mongoose = require('mongoose');
const cors = require('cors');
const config = require('./config');
const passport = require('passport');
mongoose.connect(process.env.cosmosConn || config.conn);
//App setup
app.use(morgan('combined'));
app.use(cors());
app.use(bodyParser.json({ type: '*/*' }));
app.use(passport.initialize());
app.use(passport.session());
const router = require('./router');
router(app);
//Server setup
const port = process.env.port || process.env.PORT || 3090;
const server = http.createServer(app);
server.listen(port);
console.log('Server listening on:', port);

router.js

const Authentication = require('./controllers/authentication');
const passport = require('passport');
const User = require('./models/user');
const PlotCast = require('./models/plotCast');
const requireAuth = passport.authenticate('jwt', { session: true });
const requireSignin = passport.authenticate('local', { session: true });
const mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
const passportService = require('./services/passport');

module.exports = function(app) {
    app.get('/',
        requireAuth,
        (req, res) =>
        {
            res.send({
                message: `Welcome to Trellis, ${req.user.email}! Here's what's going on with your plots:`
            });
        });
    app.post('/signin', requireSignin, Authentication.signin);
    app.post('/signup', Authentication.signup);
    app.post('/confirmation', Authentication.confirmation);
    app.post('/resend', Authentication.resend);
    app.post('/verify-email', Authentication.verifyEmail);
    app.post('/resend-verify-code', Authentication.resend);
    app.post('/reset-password', Authentication.resetPassword);
    app.post('/reset-password/verify', Authentication.verifyResetPassword);
    app.post('/reset-password/new', Authentication.resetPasswordNew);
    app.get('/plots',
        requireAuth,
        async (req, res) =>
        {
            const user = await User.findOne({ email: req.user.email }).lean()
            const company = user.company;
            const plotcast = await PlotCast.find({ owner: company }).lean();
            if (!plotcast) { throw new Error('Plot Casts not found') }
            else {res.send(plotcast); }
        });
    app.get(
        '/auth/google',
        passport.authenticate('google', {
        scope: ['profile', 'email']
    })
    );
    app.get('/auth/google/callback', passport.authenticate('google'));
    app.get('/api/current_user',(req, res) =>
    {
        console.log(req);
        res.send(req.user);
    })

    app.get('/api/logout', (req, res) =>
    {
        req.logout();
        res.send(req.user);
    })
};

passport.js

const passport = require('passport');
const User = require('../models/user');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const LocalStrategy = require('passport-local');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const config = require('../config');

const localOptions = { usernameField: 'email' };
const localLogin = new LocalStrategy(localOptions,
    async (email, password, done) =>{
        const existingUser = await User.findOne({ email: email })
        if (!existingUser) { return done(null, false); }
        const passwordMatch = await existingUser.comparePassword(password);
        if (!passwordMatch) { return done(null, false); }
        return done(null, user);
});

const jwtOptions = {
    jwtFromRequest: ExtractJwt.fromHeader('authorization'),
    secretOrKey:  config.secret
};

const jwtLogin = new JwtStrategy(jwtOptions,
    async (payload, done) =>
    {
        try
        {
            const user = await User.findById(payload.sub);
            if (user)
            {
                done(null, user);
            } else
            {
                done(null, false);
            }
        }
        catch (e)
        {
            return done(e, false);
        }
});

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

passport.deserializeUser(async (id, done) =>
{
    const user = await User.findById(id)
    if(user){ done(null, user); }
});

passport.use(new GoogleStrategy(
    {
        clientID: config.googleClientID,
        clientSecret: config.googleClientSecret,
        callbackURL: '/auth/google/callback',
        proxy: true
    }, async (accessToken, refreshToken, profile, done) =>
    {
        const existingUser = User.findOne({ googleId: profile.id })
        if (existingUser)
        {
            done(null, existingUser)
        } else
        {
            const user = await new User({ googleId: profile.id }).save()
            done(null, user);
        }
    }
));

passport.use(jwtLogin);
passport.use(localLogin);

EDIT: refactored code for Async/Await

Upvotes: 1

Views: 1829

Answers (1)

James
James

Reputation: 3815

Once Google hits your callback route, you have to generate and send the token to the user. You might want to redirect the user (res.redirect) to a new route that handles the token exchange.

In other words, you might want to implement this logic inside the Google callback function itself:

app.get( '/auth/google/callback',
        passport.authenticate('google', {
            //successRedirect: '/',
            failureRedirect: '/'
            , session: false
        }),
        function(req, res) {
            var token = TokenService.encode(req.user);
            res.redirect("/api?token=" + token);
        });

Upvotes: 3

Related Questions