Passport FacebookTokenError due to Chrome preloading - node.js

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!

I would leave this as a comment but I don't have the reputation. But Chrome will only prefetch pages when you're typing something into the URL bar, but why would you or a user manually type in /auth/facebook?
One possible solution would be to make the /auth/facebook route only accept POST requests. That would keep Chrome from being able to trigger the route when it tries to preload.
Another possible solution, and I'm not sure how well this would work, would to require a timestamp in the query string, something like /auth/facebook?_t=1406759507255. And only call passport.authenticate('facebook') when the timestamp is close enough to the current time. But I don't think either of these solutions are necessary simply because no one should be typing in that URL at all.

Related

How to send a flash error when passport.deserializeUser() fails?

Using passport in an express app. For reasons, the session tokens expire after one hour.
If the user is active when the session expired, the deserialize function "fails", i.e., user is undefined.
passport.deserializeUser(function (id, done) {
const user = sessionManager.userLookup(id);
done(null, user);
});
The trouble is that when user is undefined, then there is no req.user for subsequent middleware. So to the code it simply appears that the user is not signed in, with no breadcrumbs to indicate that the session just expired. The app simply redirects all request from unauthenticated users to /login.
For a user in the middle of a workflow, this experience is sub-optimal.
The expiration can be detected within passport.deserializeUser() like this:
passport.deserializeUser(function (id, done) {
const user = sessionManager.userLookup(id);
const errorInfo = ( expire logic check ) ? 'session has expired' : null;
done(errorInfo, user);
});
I can get the logic check right with the sessionManager. The trouble with this solution is that passport sends the user a 500 Internal Server Error, which is also sub-optimal.
What I would like is for the app to send a flash error saying the session has expired. But passport.deserialize() has no visibility to the req object for calling req.flash().
At this point the only way I can think to resolve the issue is to insert a middleware before passport, where the code would lookup the user in the session manager and call req.flash() if the session has expired. It seems like passport should provide a better way to handle such errors.
Answers would be extra-helpful if they include a link to documentation for passport.deserialize(). The only docs I have found here make no mention of how passport handles errors or if it is possible to configure or override the behavior.
UPDATE
After some reflection, flash is not the best mechanism for reporting the session expiration. The app should instead redirect to a "session expired" page. However, the main question still stands. The call changes from req.flash() to res.redirect(), but neither of these objects is available in passport.deserialize().
you can add req as a first parameter in the function, like this:
(also, recommend you use arrow function)
passport.deserializeUser(async (req, id, done) => {
req.flash('error', {});
(...)
});

How is passport receiving the profile?

I have implemented passport with GitHub-Strategy. Here's the Glith.
It works wonderfully and I'm receiving the user-profile on redirect from GitHub.
Now, I'm only trying to understand how this actually works 'under the hood'.
I did not find any similar question here on stackoverflow, neither on Passport.
So if I open Chrome Developer Tools during the Auth-Flow, the following seems to be going on when I click on Login with GitHub:
the node route /auth/github is called
node redirects to https://github.com/login/oauth/authorize?response_type=code&redirect_uri=https%3A%2F%2Frightful-exclusive-carriage.glitch.me%2Fauth%2Fgithub%2Fcallback&client_id=ccfcc73fac8223317176
the user is presented with GitHub-Login-Page
User types in GitHub-credentials and clicks 'Login'
GitHub checks the credentials
If valid credentials are provided, user is authenticated and GitHub redirects to the registered callback-endpoint, which is in my case: https://rightful-exclusive-carriage.glitch.me/auth/github/callback
The callback-url has a url-parameter, e.g. ?code=02337a951c242b9202fd. It's interesting to note, that it's a GET-method and nothing else is provided.
On the server, the passport.authenticate('github', ...) method is called inside of the /auth/github/callback-route.
When the GithubStrategy is instanciated, a callback-function is passed with the signature function(accessToken, refreshToken, profile, cb). Somehow magically, the accessToken and profile are fully available here. And I don't understand how this happens.
How is passport receiving the profile?
Is node.js making a server-side call to GitHub? Maybe with the ?code=<id> ?
Yeah that is exactly what NodeJS is doing. This doesn't have anything to do with Passport.JS or Node.JS. It is the OAuth mechanism of how authorizations work.
Whenever a the Identity provider like twitter/facebook calls your /callback with a ?code= query param It then hits another url and gets the AccessToken, RefreshToken and Idtoken(which is basically the user profile).
You can check out in the source code as well:
In this strategy.js#L157 and strategy.js#L173 of passports oauth strategy:
if (req.query && req.query.code) {
....
self._oauth2.getOAuthAccessToken(code, params,
function(err, accessToken, refreshToken, params) {
....
}
....
}
And the oauth2.js#L177 and oauth2.js#L190 of node-oauth package can see that:
exports.OAuth2.prototype.getOAuthAccessToken= function(code, params, callback) {
...
this._request("POST", this._getAccessTokenUrl(), post_headers, post_data, null, function(error, data, response) {
....
})
}
Bascially a POST request is being sent to the accessTokenUrl.

what does req.login do in passport

I am using multiple passport stratergy across my app.
Now, since I am using multiple passport strategy to connect (and not to just sign-in), I decided to Google things on how to do it.
This is where I stumbled upon this code
passport.authenticate('meetup', (err, user, info) => {
if (err) { return next(err); }
if (!user) { return res.redirect(process.env.CLIENT_ADDRESS); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect(process.env.CLIENT_ADDRESS);
});
Here I am unable to comprehend what is happening, like for first question, what is if (!user), Does it mean req.user
Second, there is req.logIn()
According to passport docs,
Passport exposes a login() function on req (also aliased as logIn())
that can be used to establish a login session.
and
When the login operation completes, user will be assigned to req.user.
Then what is the difference between using serializer/deserializer when compared with req.login?
Also in the callback, we can always do this
passReqToCallback: true
}, (req, accessToken, refreshToken, params, profile, cb) => {
to get req
To summarize can someone please help me comprehend the above code snippet?
At a high level Passport.js is a middleware that "serializes" a user identity in a request/response header (usually a session cookie). This serializing step means that it's taking the login information that identifies a user and produces a new object that represents the user. Think of this object as a key 🔑 card that only Passport will know how to interpret.
When a user makes additional API requests they pass that same identification header back. Passport auths the request by "deserializing" it to identify what user is making that request.
req.login() is the magic that is generating a session for a user. This session represents how long a login is good for without having to re-authenticate.
Let's take a look at the beginning of your snippet:
passport.authenticate('meetup', (err, user, info) => {
...
if (!user) { return...
In this snippet, passport is being set up as middleware. When a request comes through, passport behind the scenes has already interpreted the request header by deserializing the cookie and determines if it represents a user. If there is not a user or the request header does not represent a user, the request is not authorized.
req.login aliased as req.logIn
Passport exposes a login() function on req (also aliased as logIn()) that can be used to establish a login
session.
When the login operation completes, user will be assigned to req.user
Note: passport.authenticate() middleware invokes req.login() automatically.
Use a lower version of passport for this feature v0.4.1
You can install this with
npm install passport#^0.4.1
Your req.login function should work with that version.

Passport.js strategy fails when not using session

I'm trying to figure out how to integrate a Oauth strategy(github) to my application which uses express and websockets.
I'm following this guide which explains how to use JWT tokens instead of using the default passport sessions
https://blog.hyphe.me/token-based-authentication-with-node/
this is the code i have so far
app.use(passport.initialize())
app.get('/auth/github',passport.authenticate('github',{session:false}),serialize, generateToken, respond)
app.get('/auth/github/callback',passport.authenticate('github',{failureRedirect:'/'}),
function(req,res){
res.redirect('/')
}
)
When i try to login via github - i get the below error
Error: Failed to serialize user into session
at pass (/home/avernus/Desktop/experiments/oauth/node_modules/passport/lib/authenticator.js:271:19)
at Authenticator.serializeUser (/home/avernus/Desktop/experiments/oauth/node_modules/passport/lib/authenticator.js:289:5)
at IncomingMessage.req.login.req.logIn (/home/avernus/Desktop/experiments/oauth/node_modules/passport/lib/http/request.js:50:29)
at Strategy.strategy.success (/home/avernus/Desktop/experiments/oauth/node_modules/passport/lib/middleware/authenticate.js:235:13)
at verified (/home/avernus/Desktop/experiments/oauth/node_modules/passport-oauth2/lib/strategy.js:177:20)
at Strategy._verify (/home/avernus/Desktop/experiments/oauth/passport.js:13:12)
at /home/avernus/Desktop/experiments/oauth/node_modules/passport-oauth2/lib/strategy.js:193:24
at /home/avernus/Desktop/experiments/oauth/node_modules/passport-github/lib/strategy.js:174:7
at passBackControl (/home/avernus/Desktop/experiments/oauth/node_modules/oauth/lib/oauth2.js:125:9)
at IncomingMessage.<anonymous> (/home/avernus/Desktop/experiments/oauth/node_modules/oauth/lib/oauth2.js:143:7)
I'm not sure where exactly the problem is
this is my github strategy
passport.use(new githubStrategy({
clientID:'********',
clientSecret:'*******',
callbackURL:'http://localhost:3000/auth/github/callback'
},
function(accessToken,refreshToken,profile,done){
console.log('accessToken: ',accessToken,' refreshToken: ',refreshToken,' profile: ',profile)
return done(null,profile)
}
))
I'm able to successfully get the profile from github
the serialize function
function serialize(req, res, next) {
db.updateOrCreate(req.user, function(err, user){
if(err) {return next(err);}
// we store the updated information in req.user again
req.user = {
id: user.id
};
next();
});
}
from my experience passportjs with oauth always requires sessions to operate, despite the session: false option.
i believe the underlying oauth library dependencies look for sessions no matter what. its quite frustrating.
edit: to add more detail to this, the example you are linking to uses the default strategy, which is not oauth based. in this instance you could opt out of using sessions. you are using the github strategy which uses oauth thus requires sessions
Aren't you missing the {session:false} option in your callback?
app.get('/auth/github/callback',passport.authenticate('github',{failureRedirect:'/', session: false}),
function(req,res){
res.redirect('/')
})
Im guessing right here because I've never worked with Strategies that requires a callback. But i would imagine that passport tries to serialize the user in the callback as thats the point where you receive the profile from Github.

Passport.js: passport-facebook-token strategy, login through JS SDK and THEN authenticate passport?

I was looking for a way to let my client authorize with the facebook JS SDK and then somehow transfer this authorization to my node server (so it can verify requests with the fb graph api)
I stumbled across:
https://github.com/jaredhanson/passport-facebook/issues/26
&
https://github.com/drudge/passport-facebook-token
what seems to be an entirely different strategy from passport-facebook.
Am I correct when assuming that:
One logs in with the fb JS SDK, and then the facebook-token strategy somehow extracts the token and fb id from the document or body object?
Or is there any other decent way to achieve this? I'm namely trying to avoid the redirects enforced by the server SDKs
I've spent a couple of days this week trying to figure out the best way to use Facebook Authentication for a private API, using passport.js — passport-facebook-token is perfect for this.
You are correct in assuming these are two separate authentication strategies. You don't need passport-facebook installed to use passport-facebook-token.
If you have Facebook authentication implemented in the client-side JS (or iOS etc.), and are looking for a way to then authenticate API requests using your user's Facebook authToken, passport-facebook-token is a really elegant solution.
passport-facebook-token works totally independently of passport-facebook, and basically handles the redirects required by Facebook internally, before passing the request along to your controller.
So to authenticate an API route using passport-facebook-token, you'll need to set up a passport strategy like so:
passport.use('facebook-token', new FacebookTokenStrategy({
clientID : "123-your-app-id",
clientSecret : "ssshhhhhhhhh"
},
function(accessToken, refreshToken, profile, done) {
// console.log(profile);
var user = {
'email': profile.emails[0].value,
'name' : profile.name.givenName + ' ' + profile.name.familyName,
'id' : profile.id,
'token': accessToken
}
// You can perform any necessary actions with your user at this point,
// e.g. internal verification against a users table,
// creating new user entries, etc.
return done(null, user); // the user object we just made gets passed to the route's controller as `req.user`
}
));
It's worth noting that the User.findOrCreate method used in the passport-facebook-token Readme is not a default mongo/mongoose method, but a plugin that you'll have to install if you want it.
To use this auth strategy as middleware for any of your routes you'll need to pass it an access_token object either as a URL parameter, or as a property of the request body.
app.get('/my/api/:access_token/endpoint',
passport.authenticate(['facebook-token','other-strategies']),
function (req, res) {
if (req.user){
//you're authenticated! return sensitive secret information here.
res.send(200, {'secrets':['array','of','top','secret','information']});
} else {
// not authenticated. go away.
res.send(401)
}
}
NB. the access_token property is case-sensitive and uses an underscore.
The documentation for passport-facebook-token isn't extensive, but the source is really well commented and pretty easy to read, so I'd encourage you to take a look under the hood there. It certainly helped me wrap my head around some of the more general ways that passport works.

Resources