PassportJS with Facebook strategy, is my code correct? - node.js

I use NodeJs with passportJS in my app to allow users to login/register using Facebook. It seems to work fine, not sure if I understand fully what is happening there though. So the idea is that users can try to login from different pages in my app using Facebook details. After Facebook returns me all the details I log them in or register new user using what Facebook returned and after this is done redirect them to same page but being already logged in. The code is:
var passport = require('passport'),
FacebookStrategy = require('passport-facebook').Strategy;
function setupFBStrategy(req, res, next) {
return function(req, res, next) {
var redirectUrl = req.params.currentPage;
console.log('setupFBStrategy')
passport.use(new FacebookStrategy({
clientID: 'abc',
clientSecret: 'def',
// step 2 ################################
callbackURL: "/auth/facebook/callback?currentPage="+redirectUrl,
passReqToCallback: true,
profileFields: ['first_name', 'last_name', 'photos', 'email']
},
function(req, accessToken, refreshToken, profile, done) {
// step 5 ###########################
// verify callback. Now I have user email inside 'profile' and I authenticate this aginst my database. I use `done(..)` to further instruct my app if the registration/authentication with my system was success or not
}
));
next();
}
}
app.use(passport.initialize());
// step 1 ################################
app.get('/auth/facebook/cp/:currentPage', setupFBStrategy(), passport.authenticate('facebook', { authType: 'rerequest', scope: ['email'] }));
app.get('/auth/facebook/callback',
function(req, res, next) {
console.log(req.query.currentPage)
passport.authenticate('facebook', {
successRedirect: '/'+req.query.currentPage, // redirects when user allowed or logged in with username password
failureRedirect: '/'+req.query.currentPage // takes here when user clicks cancel when asked to give permissions
})(req,res,next);
}
);
passport.serializeUser(function(user, done) {
console.log('serialize='+user)
done(null, {});
});
passport.deserializeUser(function(user, done) {
console.log('deserialize=' + user);
done(null, {});
});
The steps are:
in my app user clicks "login/register with facebook" that makes request to auth/facebook/cp route.
route calls setUpFBStrategy. I append current page user is looking at to callbackUrl.
PassportJS send redirect back to user browser and user browser redirects to facebook for authentication.
when facebook is finished authenticating user it sends redirect to user browser so the browser redirects to callbackURL with URL specified in step 2. It also appends '?code=' querystring to callback URL. Is this querystring hashed version of what Facebook returns so in my case public info and email?
Now my server 'auth/facebook/callback' is executed and verify callback is executed. Depending if I call done(null,profile) etc inside verify callback server returns redirect to browser and browser redirects to successRedirect or failureRedirect 'successRedirect' or 'failureRedirect' routes.
All seems to work so far but is my understanding correct? Is 'code' querystring hashed version of details facebook returns? Why do I even need serializeUser and deserializeUser functions in my code? When would I use refreshToken and accessToken?

yes your code seems to be fine.
passport.serializeUser and passport.deserializeUser and function provided by the passport
passport.serializeUser is called once on login when you call
req.logIn(someValue,function(){})
here someValue is the value you want to store in the passport session
and on every request when you call req.isAuthenticated() function to check that the passport session exist or not the passport.deserializeUser will be called returning true or false.
and in end of the user browsing means on logout you call req.session.destroy to destroy that perticular users session.

Related

How to redirect to React/Vue route after user authorize via oauth2 twitter or discord through passport?

So I want to make authentication so that a user can authenticate by twitter and discord. So I created developer accounts in twitter and discord developer portal. then I made passport strategy for both socials. For reference I am providing my strategy credentials in the following(for twitter):
{
consumerKey: process.env.TWITTER_CONSUMER_KEY,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
callbackURL: "http://localhost:9000/api/auth/twitter/callback",
includeEmail: true,
}
the discord one is similar to twitter.
Routes for twitter are:
router.get("/auth/discord", passport.authenticate("discord"))
router.get(
"/auth/discord/redirect",
passport.authenticate("discord"),
(req, res) => {
res.json({
success: true,
user: req.user,
})
}
)
Now my question is after a user authorizes, how can I redirect the user to SPA route (React/Vue)?
As I can see in the passport.js documentation, there are two parameters available when you call
passport.authenticate function. These two are: successRedirect and failureRedirect.
The first will redirect the user to the sigin page. The second will process the authentication result when the user is redirected back.
Check this out ;) passportjs.org/tutorials/auth0/routes
That was very simple indeed. I just needed to add redirect to my frontend url
router.get("/auth/twitter", passport.authenticate("twitter"))
router.get(
"/auth/twitter/callback",
passport.authenticate("twitter", {
failureRedirect: "http://localhost:3000/login",
}),
(req, res) => {
res.redirect("http://localhost:3000")
}
)

Passport login and persisting session

Background
I have a MEAN application with CRUD capabilities fully tested with postman. I have been trying to persist login for quite some time now with no luck. I have read and tried the following
passport docs
toon io's blog about login
Express session
Scotch io, node auth made easy
Plus a buch of other light reading materials (Lots of SO questions)
But I have only been able to register and log a user in, not persist login with a session.
My App
Here is a link to the full github repo (if you are looking for the latest changes check develop branch)
My Understanding of Auth/Login
Here is my understanding of user login with code examples from my project and screenshot of postman results as well as console logs.
Passport setup
I have the following auth.js file, it configs passport
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
module.exports = function(app, user){
app.use(passport.initialize());
app.use(passport.session());
// passport config
passport.use(new LocalStrategy(user.authenticate()));
passport.serializeUser(function(user, done) {
console.log('serializing user: ');
console.log(user);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
user.findById(id, function(err, user) {
console.log('no im not serial');
done(err, user);
});
});
};
This gets called in the server file like
//code before
var user = require('./models/user.js');
var auth = require('./modules/auth.js')(app, user);
// code after
Routing for login
In my routes I have the login route as follows
router.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.status(401).json({
err: info
});
}
req.logIn(user, function(err) {
if (err) {
return res.status(500).json({
err: 'Could not log in user'
});
}
res.status(200).json({
status: 'Login successful!'
});
});
})(req, res, next);
});
This route works as tested with postman. I enter the details 'joe' and 'pass' and get the following response.
When this route is hit we can also see in the console that the user is serialized.
So what next?
This is where I get lost. I have a few questions.
Is the user now in a session on my server?
Should I send the req.session.passport.user back to the client?
Do I need the session ID on all future requests?
Testing the Session
I have a second route setup for testing the session it is as follows
router.get('/checkauth', passport.authenticate('local'), function(req, res){
res.status(200).json({
status: 'Login successful!'
});
});
The part passport.authenticate('local') (I thought) is there to test if the user session exists before giving access to the route but I never get a 200 response when I run this, even after a login.
Does this route expect a req.session.passport.user passed in the head or as a data argument on a http request that requires auth?
If I missed anything or am understanding something wrong please tell me, any input is appreciated. Thanks all.
Is the user now in a session on my server?
No, You need to use the express-session middleware before app.use(passport.session()); to actually store the session in memory/database. This middleware is responsible for setting cookies to browsers and converts the cookies sent by browsers into req.session object. PassportJS only uses that object to further deserialize the user.
Should I send the req.session.passport.user back to the client?
If your client expects a user resource upon login, then you should. Otherwise, I don't see any reason to send the user object to the client.
Do I need the session ID on all future requests?
Yes, for all future requests, the session id is required. But if your client is a browser, you don't need to send anything. Browser will store the session id as cookie and will send it for all subsequent requests until the cookie expires. express-session will read that cookie and attach the corresponding session object as req.session.
Testing the Session
passport.authenticate('local') is for authenticating user credentials from POST body. You should use this only for login route.
But to check if the user is authenticated in all other routes, you can check if req.user is defined.
function isAuthenticated = function(req,res,next){
if(req.user)
return next();
else
return res.status(401).json({
error: 'User not authenticated'
})
}
router.get('/checkauth', isAuthenticated, function(req, res){
res.status(200).json({
status: 'Login successful!'
});
});
As #hassansin says you need to use a middleware that implement session management. The passport.session() middleware is to connect the passport framework to the session management and do not implement session by itself. You can use the express-session middleware to implement session management. You need to modify your auth.js in the following way
var passport = require('passport');
var session = require('express-session');
var LocalStrategy = require('passport-local').Strategy;
module.exports = function(app, user){
app.use(session({secret: 'some secret value, changeme'}));
app.use(passport.initialize());
app.use(passport.session());
// passport config
passport.use(new LocalStrategy(user.authenticate()));
passport.serializeUser(function(user, done) {
console.log('serializing user: ');
console.log(user);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
user.findById(id, function(err, user) {
console.log('no im not serial');
done(err, user);
});
});
};
Notice that in this case the session engine is using the in memory store and it didn't work if you scale your application and apply load balancing. When you reach this development state something like the connect-redis session store will be needed.
Also notice that you need to change the secret value used on the session midleware call and use the same value on all application instances.
As per the passport documentation, req.user will be set to the authenticated user. In order for this to work though, you will need the express-session module. You shouldn't need anything else beyond what you already have for passport to work.
As far as testing the session, you can have a middleware function that checks if req.user is set, if it is, we know the user is authenticated, and if it isn't, you can redirect the user.
You could for example have a middleware function that you can use on any routes you want authenticated.
authenticated.js
module.exports = function (req, res, next) {
// if user is authenticated in the session, carry on
if (req.user) {
next();
}
// if they aren't redirect them to the login page
else {
res.redirect('/login');
}
};
controller
var authenticated = require('./authenticated');
router.get('/protectedpage', authenticated, function(req, res, next) {
//Do something here
});
I don't know of a way to check all existing sessions, but Passport is handling the issuing of session ids. Try checking that you have req.user on your test endpoint after logging in

FacebookTokenError: This authorization code has been used

Ok, so this is a common error with many causes. I am trying to modify an existing Node-Passport-Facebook module to have local images from the desktop uploaded to a users Facebook account after they log in. That is my goal. This is the code module I am extending
https://github.com/passport/express-4.x-local-example
which in turn is based on
https://github.com/jaredhanson/passport-facebook
I never get past console.log('ERROR HERE... with an error of "This authorization code has been used."
What's confusing is that the auth code returned is ALWAYS DIFFERENT! so how could it already have been used when I try and exchange it for an access token?
Can anyone offer some suggestions, and or next steps I might try? My hunch is that there is something about Passport.js that is not implemented properly.
So my question is, how would I modify the code below (based on this passport facebook example) https://github.com/passport/express-4.x-facebook-example/blob/master/server.jsto upload an image after logging in?
var express = require('express');
var passport = require('passport');
var Strategy = require('passport-facebook').Strategy;
var CLIENTSECRET ='<client secret>';
var APPID ='<app id>';
// Configure the Facebook strategy for use by Passport.
//
// OAuth 2.0-based strategies require a `verify` function which receives the
// credential (`accessToken`) for accessing the Facebook API on the user's
// behalf, along with the user's profile. The function must invoke `cb`
// with a user object, which will be set at `req.user` in route handlers after
// authentication.
passport.use(new Strategy({
clientID: APPID,
clientSecret: CLIENTSECRET,
callbackURL: 'http://localhost:3000/login/facebook/return',
enableProof: true
//callbackURL: 'http://localhost:3000/login/facebook/return'
},
function(accessToken, refreshToken, profile, cb) {
// In this example, the user's Facebook profile is supplied as the user
// record. In a production-quality application, the Facebook profile should
// be associated with a user record in the application's database, which
// allows for account linking and authentication with other identity
// providers.
cb(null, profile);
}));
// Configure Passport authenticated session persistence.
//
// In order to restore authentication state across HTTP requests, Passport needs
// to serialize users into and deserialize users out of the session. In a
// production-quality application, this would typically be as simple as
// supplying the user ID when serializing, and querying the user record by ID
// from the database when deserializing. However, due to the fact that this
// example does not have a database, the complete Twitter profile is serialized
// and deserialized.
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
console.log(" ");
console.log("ASSERT passport.deserializeUser being called");
console.log(" ");
cb(null, obj);
});
// Create a new Express application.
var app = express();
// Configure view engine to render EJS templates.
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// Use application-level middleware for common functionality, including
// logging, parsing, and session handling.
app.use(require('morgan')('combined'));
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
// Initialize Passport and restore authentication state, if any, from the
// session.
app.use(passport.initialize());
//app.use(passport.session());
// Define routes.
app.get('/',
function(req, res) {
res.render('home', { user: req.user });
});
app.get('/login',
function(req, res){
res.render('login');
});
app.get('/login/facebook',
passport.authenticate('facebook'));
app.get('/login/facebook/return',
passport.authenticate('facebook', { failureRedirect: '/login' }),
function(req, res) {
//my code changes start here!!
var code = req.query.code;
console.log("1 ASSERT after successful login! code="+code);
if(req.query.error) {
// user might have disallowed the app
return res.send('login-error ' + req.query.error_description);
} else if(!code) {
return res.redirect('/');
}
var options={
host:'graph.facebook.com',
path:'/oauth/access_token?client_id='+APPID+'&code='+code +'&client_secret='+CLIENTSECRET+'&redirect_uri=http://localhost:3000/login/faceboo k/return'
}
var https=require('https');
https.get(options,function(res){
res.setEncoding('utf8');
res.on('data', function (chunk) {
console.log('ERROR HERE'+chunk);
});
});
console.log("2 ASSERT after successful login!")
//my code changes end here!!
});
app.get('/profile',
require('connect-ensure-login').ensureLoggedIn(),
function(req, res){
res.render('profile', { user: req.user });
});
app.listen(3000);
You don't need to make a request to /oauth/access_token at all (well you do, but passport has already handled it for you). That endpoint is for getting an access token when you don't have one, but you already have an access token here:
passport.use(new Strategy({
clientID: APPID,
clientSecret: CLIENTSECRET,
callbackURL: 'http://localhost:3000/login/facebook/return',
enableProof: true
//callbackURL: 'http://localhost:3000/login/facebook/return'
},
function(accessToken, refreshToken, profile, cb) {
// You have the access token here!
cb(null, profile);
}));
You'll need to save that accessToken some way, so that you can use it later when you make requests to the Graph API. You'll probably want to save it to the user's session, but you can also use a strategy like this: https://stackoverflow.com/a/24474900/772035
If you want the user to grant permission to publish (which you will need them to do, to be able to post to their feeds) you also need to replace every call to passport.authenticate with:
passport.authenticate('facebook', { scope: ['publish_actions'] } );
So that the posting permission is requested when the user first adds your app. Then you'll be able to use the /user/photos endpoint to upload a photo, passing the accessToken that you saved earlier in the query string.
You need to encode your query params.
var qs = {
client_id: APPID,
redirect_uri: 'http://localhost:3000/login/facebook/return',
client_secret: CLIENTSECRET,
code: code,
};
options = {
host:'graph.facebook.com',
path:'/oauth/access_token?' + require('querystring').stringify(qs),
};
I think that's your problem. The code itself looks fine apart from that. You'll want the querystring module to parse the results too by the way.
I solved this error. Follow the progress.
Facebook Log in -> Settings -> Apps -> Logged in with Facebook -> Delete Your apps.
After deleting your apps, Try to login with Facebook button.
you need to define profileFields instead of enableProof: true
passport.use(new FacebookStrategy({
clientID:keys.FacebookAppID,
clientSecret:keys.FacebookAppSecret,
callbackURL:'http://localhost:3000/auth/facebook/callback',
profileFields:['email','name','displayName','photos']
},(accessToken,refreshToken,profile,done)=>{
console.log(profile);
}));

passportjs get users twitter credentials

I want to authenticate a user via passport's twitter strategy, but not sign the user in. All I want to do is store their credentials so I can tweet to their account at a later date, but I'm not seeing how this is possible.
It looks like you have to call the done callback which then stores the users id in the session. My user is already authenticated with my application, but wants to attach one or more twitter account that they can choose to tweet to at a later date.
Here's my Twitter strategy
passport.use(new TwitterStrategy({
consumerKey: '...',
consumerSecret: '...',
callbackURL: '/add/twitter/callback',
passReqToCallback: true
},
function(req, token, tokenSecret, profile, done) {
process.nextTick(function() {
//save the users twitter credentials for use later on and call
//my custom callback here ...
});
});
}));
I'm also using express.js so here is are my routes
router
.get('/auth/twitter', passport.authenticate('twitter'))
.get('/auth/twitter/callback',
passport.authenticate('twitter'), function(req, res) {
console.log('made it here');
// Successful authentication
res.render('manager/add-twitter', {});
});
Is it possible to have a custom callback that gets fired no matter what?
As it turns out, you can do this using authorize instead of authenticate. Here's the docs for anyone who's interested: http://passportjs.org/guide/authorize/

PassportJS - FacebookTokenStrategy returns 404

I am using PassportJS to handle FB authentication for both browser and mobile clients. For web users I am using the Passport FacebookStrategy and this is working as intended. I would also like to allow mobile clients to access my API. I am trying to use Passport FacebookTokenStrategy to facilitate this. This seems to be working with one small issue. When a mobile client makes a GET request to the server the FacebookTokenStrategy is used and the verify callback function is invoked. In the verify function I can see that the user profile is available and therefore the authentication has succeeded. However an HTTP status of 404 is sent back in the response to the mobile client. I'm not sure how to configure this properly. This is what I'm trying currently:
// Web based auth
passport.use(new FacebookStrategy({
clientID: Config.facebook.clientID,
clientSecret: Config.facebook.clientSecret,
callbackURL: "http://localhost/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, done) {
User.findOrCreate(profile, function(err, user){
done(err, user);
});
}
));
// Mobile client auth
passport.use(new FacebookTokenStrategy({
clientID: Config.facebook.clientID,
clientSecret: Config.facebook.clientID
},
function(accessToken, refreshToken, profile, done) {
console.log(profile);
User.findOrCreate(profile, function(err, user){
done(err, user);
});
}
));
// Redirect the user to Facebook for authentication. When complete,
// Facebook will redirect the user back to the application at
// /auth/facebook/callback
exports.fb_auth = passport.authenticate('facebook',{ scope: 'email' });
// Facebook will redirect the user to this URL after approval. Finish the
// authentication process by attempting to obtain an access token. If
// access was granted, the user will be logged in. Otherwise,
// authentication has failed.
exports.fb_callback = passport.authenticate('facebook', { successRedirect: '/',
failureRedirect: '/login' });
// Mobile Authentication
exports.mobile_fb_auth = passport.authenticate('facebook-token');
Should I be providing passport.authenticate('facebook-token'); with some additional 'onsuccess' callback? That makes sense in the context of a web client but I'm not sure how this should be handled using the facebook-token strategy.
I just had the same issue and was able to resolve it. The 404 is returned because of how middleware works in express. You need to pass in a third function that responds with a success.
The third function isn't called always. It's only called when the previous middleware succeeds.
apiRouter.get('/auth/facebook',
// authenticate with facebook-token.
passport.authenticate('facebook-token'),
// if the user didn't successfully authenticate with the above line,
// the below function won't be called
function(req, res){
res.send(200);
});
`

Resources