I am working on a web app which which allows user logins through Facebook using Passport.js. My code is as follows:
/* Passport.js */
var passport = require('passport');
var FacebookStrategy = require('passport-facebook').Strategy;
/* DB */
var User = require('../models/db').User;
exports.passport = passport;
passport.use(new FacebookStrategy(
{
clientID: '<ID>',
clientSecret: '<SECRET>',
callbackURL: 'http://localhost:4242/auth/facebook/callback'
},
function (accessToken, refreshToken, profile, done) {
console.log(profile.provider);
User.findOrCreate({ "provider": profile.provider,"id": profile.id },
function (err, user) { return done(err, user); });
}
));
passport.serializeUser(function(user, done) {
console.log('serialize');
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log('deserialize');
User.findOne({"id": id}, function(err, user) {
done(err, user);
});
});
This code works fine on Firefox; my user authenticates through Facebook and then routes successfully. On Chrome, however, I sometimes get the following error:
FacebookTokenError: This authorization code has been used.
at Strategy.parseErrorResponse (/Users/Code/Web/node_modules/passport-facebook/lib/strategy.js:198:12)
at Strategy.OAuth2Strategy._createOAuthError (/Users/Code/Web/node_modules/passport-facebook/node_modules/passport-oauth2/lib/strategy.js:337:16)
at /Users/Code/Web/node_modules/passport-facebook/node_modules/passport-oauth2/lib/strategy.js:173:43
at /Users/Code/Web/node_modules/passport-facebook/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:162:18
at passBackControl (/Users/Code/Web/node_modules/passport-facebook/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:109:9)
at IncomingMessage.<anonymous> (/Users/Code/Web/node_modules/passport-facebook/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:128:7)
at IncomingMessage.EventEmitter.emit (events.js:117:20)
at _stream_readable.js:910:16
at process._tickCallback (node.js:415:13)
My print statements reveal some rather unexpected behavior, as depicted in the pictures below:
The unfinished URL waiting to be submitted...
...results in print statements in my terminal.
It seems that Chrome attempts to preload the request to Facebook, causing a race condition resulting in an error if the client presses enter at just the right time, as shown below:
I have confirmed the multiple requests with Wireshark. If I wait long enough between autocompletion and submission of the URL (say, 3 seconds), both requests complete without error. The error only occurs if the two requests send just over a second apart. The error is unique to Chrome, as Firefox only sends one request.
Is there anything I can do here? My app surely cannot be the only one which experiences this error when it comes to something as frequent as Facebook authentication. Can I prevent Chrome from preloading somehow? If not, am I resigned to catching the error and just trying to authenticate again?
Bonus question: I seem to be deserializing multiple times for each request. My very first request will print the following:
facebook
serialize
deserialize
Every subsequent successful request prints
deserialize
deserialize
facebook
serialize
deserialize
while unsuccessful request pairs print
deserialize
deserialize
deserialize
deserialize
/* Error */
facebook
serialize
It looks like each request deserializes twice. I read this bug report suggesting a solution, but express.static
does come before passport.session
in my middleware stack, so that cannot be my problem.
Thanks!