How to pass extra data in passport-facebook authentication - javascript

I'm trying to write an web application in Node.js which uses passport-facebook authentication strategy. By default the mechanism provided in the example works by redirecting user to Facebook API where user can put there username and password to login. Once Facebook verifies the user, it passes the token back to a callback URL in my application.
Now these things works for my application. I can login using Facebook with no problem. But I want one extra field to be passed to the server when I try to login.
Basically my plan is to add a TextField and a Login Button in my homepage. After successful login, when the callback function is executed, I want to capture the value of TextField from this callback. Is there any way of doing that? Any help is highly appreciated.
This is the body of my HTML page
<body>
<div class="container">
<div class="jumbotron text-center">
<p>Login or Register with:</p>
<span class="fa fa-facebook"></span> Facebook
</div>
</div>
</body>
And this is my nod.js code:
app.get('/auth/facebook', passport.authenticate('facebook'));
app.get('/auth/facebook/callback', passport.authenticate('facebook', {
successRedirect : '/dashboard',
failureRedirect : '/'
}));
app.get('/dashboard', isLoggedIn, function(req, res){
res.render('dashboard.ejs', {
user : {
name : req.user.facebook.name,
profilePic : req.user.facebook.profilePic
}
});
});
Basically I just want to add an extra TextField just before my LogIn button. And from the /dashboard route, I want to get the value of that TextField. So my code for /dashboard will look something like this:
app.get('/dashboard', isLoggedIn, function(req, res){
res.render('dashboard.ejs', {
user : {
role: VALUE_FROM_TEXT_FIELD,
name : req.user.facebook.name,
profilePic : req.user.facebook.profilePic
}
});
});
So I have a twofold question:
1. How to pass this TextField value from the frontend. Should it be inside a Form or what?
2. How do I capture the value of the TextField inside the /dashboard route?

Have you considered using a custom callback instead of the standard Passport.js one?
An example for the 'local' strategy is:
app.get('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
You should be able to pass in the additional value in the request and handle it in the callback.
EDIT: For your particular scenario, you're probably looking at something along the lines of this...
app.get('/auth/facebook/callback', function (req, res, next) {
passport.authenticate('facebook', function (err, user, info) {
if (err) {
return next(err);
// let the next route handler do its thing...
// or you can handle the error yourself within this
// callback.
}
// if the user returns as nothing (doesn't exist) then redirect
if (!user) {
// this takes the place of failureRedirect
return res.redirect('/');
}
req.logIn(user, function (err) {
if (err) {
return next(err); // again - on error, 'next' depending on your requirement.
}
// this takes the place of successRedirect
return res.redirect('/dashboard');
});
})(req, res, next);
});
Hopefully this clarifies :)

What i did is pass a custom function instead of using sucessRedirect and pass it the variable need. In my case it was req.session.backurl which was used to send them back to the page which they needed to be authenticated to access.
users.get('/auth/facebook/callback',
//req.session.backURL||
passport.authenticate('facebook', { failureRedirect: '/login' }),function(req,res){
res.redirect(req.session.backURL||'/'); //custom function used instead of sucessRedirect
});

Related

How do I render a pug template on success and failure of authentication(passport.js) with variables from the pug template? (express, passport.js, pug)

I am working on the login system using passport.js and express with pug templates. I am trying to render the login page with a message on failure, and if on success I want to redirect the user back to the last page they have been to, of which URL is passed as hidden input inside the pug template. I want to then take that value of that hidden input and put it inside the successRedirect, however it says req is not defined:
router.post("/login", body('backurl').trim().escape(), passport.authenticate("local", {
successRedirect: (req.body.backurl),
failWithError: true
}), function(err, req, res, next) {
return res.render('login', { message: 'Unable to login, the password or the username are wrong' })
}
from my understanding, that happens because of it not having a req since there is no function with req, res, next, so I tried to wrap it with a function that has it:
router.post("/login",
body('backurl').trim().escape(),
function(req, res, next) {
passport.authenticate("local", {
successRedirect: (req.body.backurl),
failWithError: true
}), function(err, req, res, next) {
return res.render('login', {message: 'Unable to login, the password or the username are wrong'})
}
}
);
but it just keeps reloading and doesn't work and doesn't go to any URL.
can anybody help me fix this and move the user to the URL from the hidden input while rendering a pug template on failure with variables?
Thanks!
You can render a view with a get() request.
With a post() request you should redirect and if you want to show a message to the user you could use the express-flash package.
router.post("/login", (req, res, next) => {
passport.authenticate("local", (err, user) => {
if (!user) {
req.flash('error', { msg: 'Unable to login, the password or the username are wrong')
return res.redirect('/login');
}
res.redirect(req.body.backurl);
}
});
This way you can have more details about what appened.

function for catching errors by parameters we give it as argument

I need to create authentication function named "access" for my restful API and i want it to look like below for each time user wants be in interaction with server :
access(id , token ,function(err){
if(err){
res.send(err)
}else {
res.send('you have all permissions')
}
})
how can i write this function to use in each authentication step?
For authentication you would typically have some middleware:
function isAuthenticated(req, res, next) {
// determine here if user is authenticated (and/or authorized)
if (user) {
next(); // user is authorized, call next
} else {
const error = new Error('unauthorized');
error.status = 400;
return next(err);
}
}
app.get('/authenticated/route', isAuthenticated, function(req, res) {
res.send('secret information');
});
I would recommend using something like Passport.js. It removes a lot of the authentication middleware, and especially makes it easy to integrate with providers like Google and Facebook.
Better to use is as middleware, if this is for all your entries
function access(req, res, next) {
// verify token
if (isNotVerified) { return next('You are not verified') }
// otherwise do what you want to do
return next()
}
and add it to all your routes where you want the user to be verified, someting like this:
route.get('/api/private/reports', access, function (req, res) {
// do some stuff only when user is verified
})
route.get('/api/sensitive/information', access, function (req, res) {
// do some stuff only when user is verified
})
Hope it will help you!

How to redirect and then stop in node.js

I am trying to stop users accessing certain pages whilst logged in and not logged in with node.js.
So far I have this idea:
exports.blockIfLoggedIn = function (req, res) {
if (req.isAuthenticated()) { //passport
req.flash('error', 'Sorry but you can\'t access this page');
res.redirect('/');
}
};
MA.f.auth.blockIfLoggedIn(req, res, next);
res.render('users/login', {
title: 'Login'
});
This will redirect the page and but it will also error in the console with:
Error: Can't set headers after they are sent.
I understand that this is trying to do res.render('users/login') function but as I've already set the page to redirect(/) so it can't.
There must be some way that if req.isAuthenticated() is true it will redirect and then essentially do something similar to php's exit()?
You should use middleware. Here's an example:
// you can include this function elsewhere; just make sure you have access to it
var blockIfLoggedIn = function (req, res, next) {
if (req.isAuthenticated()) {
req.flash('error', 'Sorry, but you cannot access this page.')
return res.redirect('/')
} else {
return next()
}
}
// in your routes file (or wherever you have access to the router/app)
// set up your route
app.get('/users/login', blockIfLoggedIn, function (req, res) {
res.render('somePage.ext', { title: 'Login' })
})

PassportJS redirect not working when using AngularJS $http to POST

I'm using passportJS for user authentication. This is in my app's routes, which authenticates login info.
app.post('/login', passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login' }));
The redirection works when I have my form like so:
<form action="/login" method="post">
...
</form>
But when I use Angular's ng-submit with a $http.post('/login') method, the redirect doesn't trigger. The login does work, but the redirect doesn't.
<form ng-submit="submitLogin(name,password)">
...
</form>
MainController.js
$scope.loginSubmit = function(n,p) {
$http.post('/login', {
email: n,
password: p
});
};
Why does Angular prevent the redirect from working?
I'm not sure why the redirect doesn't work with Angular, but I figured out a way to solve my problem.
We need to send stuff back to Angular, and then use $location.path() to handle the redirect.
PassportJS suggests to use a custom callback if the simple authenticate functionality isn't enough.
app.post('/login', function(req, res, next) {
passport.authenticate('local-login', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
res.send({
message: 'no user'
});
}
req.login(user, function(err) {
if (err) {
return next(err);
}
res.send({
message: 'logged in'
});
});
})(req, res, next);
});
Instead of using res.redirect(), we simply send a message back to Angular.
Controller
$http.post('/login', {
email: n,
password: p
})
.success(function(data) {
if (data.message === 'logged in') {
$location.path('/presents');
});

Node.js Authentication with Passport: How to flash a message if a field is missing?

I am using passport.js and I'd like to flash a message if the fields of my form are empty. But I don't know how to do it since passport doesn't trigger the strategy callback if those are missing. I really want this use case to be more clear, and I don't want to modify passport. I feel like there is a way to do so but I don't know where! I've tried to use the callback of the route (app.post) but it doesn't seem to work the way I tried.
Here is the authenticate function prototype:
Strategy.prototype.authenticate = function(req, options) {
options = options || {};
var username = lookup(req.body, this._usernameField) || lookup(req.query, this._usernameField);
var password = lookup(req.body, this._passwordField) || lookup(req.query, this._passwordField);
// here is my problem
if (!username || !password) {
return this.fail({ message: options.badRequestMessage || 'Missing credentials' }, 400);
}
var self = this;
function verified(err, user, info) {
if (err) { return self.error(err); }
if (!user) { return self.fail(info); }
self.success(user, info);
}
try {
if (self._passReqToCallback) {
this._verify(req, username, password, verified);
} else {
this._verify(username, password, verified);
}
} catch (ex) {
return self.error(ex);
}
};
Here is my strategy:
passport.use('local-login', new LocalStrategy({
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true
},
function(req, email, password, done) {
// ...
console.log("Hello");
User.findOne({ 'local.email' : email }, function(err, user) {
if (err)
return done(err);
// if no user is found, return the message
if (!user)
return done(null, false, req.flash('loginMessage', 'Pas d\'utilisateur avec ce login.')); // req.flash is the way to set flashdata using connect-flash
// if the user is found but the password is wrong
if (!user.validPassword(password))
return done(null, false, req.flash('loginMessage', 'Oops! Mauvais password.')); // create the loginMessage and save it to session as flashdata
// all is well, return successful user
return done(null, user);
});
}));
And finally my route:
app.get('/login', function(req, res) {
// render the page and pass in any flash data if it exists
res.render('login', { title: "Connexion", message: req.flash('loginMessage') });
});
// process the login form
app.post('/login', passport.authenticate('local-login', {
successRedirect : '/profile', // redirect to the secure profile section
failureRedirect : '/login', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}, function(err, user, info) {
// Was trying this callback, does'nt work, post callback maybe ?
console.log("Hello");
}));
You should not call req.flash in your verify callback. Instead you should return a message as shown in the documentation. Passport will put the message returned to flash message when failureFlash: true:
Setting the failureFlash option to true instructs Passport to flash an error message using the message given by the strategy's verify callback, if any.
Your revised verify callback:
passport.use('local-login', new LocalStrategy({...},
function(email, password, done) {
User.findOne({ 'local.email' : email }, function(err, user) {
if (err)
return done(err);
if (!user)
return done(null, false, {message: 'Pas d\'utilisateur avec ce login.'});
if (!user.validPassword(password))
return done(null, false, {message: 'Oops! Mauvais password.'});
return done(null, user);
});
}));
And routes:
app.get('/login', function(req, res) {
console.log(req.flash('error'));
res.send();
});
app.post('/login', passport.authenticate('local-login', {
successRedirect : '/profile',
failureRedirect : '/login',
failureFlash : true
}));
Edit:
Here's a fully working example: https://gist.github.com/vesse/9e23ff1810089bed4426
Edit:
This does not indeed answer the original question which was I am using passport.js and I'd like to flash a message if the fields of my form are empty. passport-local strategy does just execute fail if the form fields are empty, so they should be checked before the authentication middleware and set the flash message outside passport.
It's an old question, but I had trouble finding an answer. Hopefully this helps others.
I think the documentation is a little incomplete when it comes to using connect-flash. They say:
Note: Using flash messages requires a req.flash() function. Express 2.x provided this functionality, however it was removed from Express 3.x. Use of connect-flash middleware is recommended to provide this functionality when using Express 3.x.
Yet, there's no mention of using req.flash in the done() callback. Based on the scotch.io tutorial, you actually should call req.flash() right there in the callback. It works for me.
// In your strategy
...
if (user) {
return done( null, false, req.flash('loginMessage','Pas d\'utilisateur avec ce login.') );
...
You will need to use passReqToCallback of course. Also be sure failureFlash is set to true. OP is already doing these correctly.
Now you can check the flash message in the route. Note that connect-flash sends an array of messages. That could be OP's problem, if his template is expecting a string.
// In your routes
app.get('/login', function(req, res) {
// Test flash messages in the console
console.log( req.flash('loginMessage') ); // This returns an array
console.log( req.flash('loginMessage')[0] ); // This returns a string
// render the page and pass in any flash data if it exists
res.render('login', {
title: "Connexion",
message: req.flash('loginMessage')[0] // Don't forget the index!
});
});
If there's a chance of having multiple login messages on a page, pass the whole req.flash('loginMessage') array and iterate through it in your template. Below is an example using nunjucks.
Protip:
If you have many routes with flash messages, you can always set them to res.locals in a middleware route. This will not interfere with other locals, like title. Here is my implementation, using bootstrap alerts.
In my strategy:
...
if (!user){
return done( null, false, req.flash('danger','No account exists for that email.') );
}
...
In my routes.js:
// Set flash messages
router.get('*', function(req,res,next){
res.locals.successes = req.flash('success');
res.locals.dangers = req.flash('danger');
res.locals.warnings = req.flash('warning');
next();
});
// Login route
app.get('/login', function(req, res) {
res.render('login', { title: 'Login'});
});
In my nunjucks base template:
<!--Messages-->
{% for danger in dangers %}
<div class='header alert alert-danger alert-dismissible'>
<strong><i class="fa fa-exclamation-circle"></i> ERROR:</strong> {{ danger | safe }}
<i class='fa fa-times'></i>
</div>
{% endfor %}
{% for warning in warnings %}
<div class='header alert alert-warning alert-dismissible'>
<strong><i class="fa fa-check-circle"></i> Warning:</strong> {{ warning | safe }}
<i class='fa fa-times'></i>
</div>
{% endfor %}
{% for success in successes %}
<div class='header alert alert-success alert-dismissible'>
<strong><i class="fa fa-check-circle"></i> Success!</strong> {{ success | safe }}
<i class='fa fa-times'></i>
</div>
{% endfor %}
You need to set badRequestMessage and set failureFlash: true.
Like this:
passport.authenticate('login', {
successRedirect : '/',
failureRedirect : '/login',
badRequestMessage : 'Missing username or password.',
failureFlash: true
})
After months of on and off trying to get failure flash to work, i finally found a solution which doesnt use the failureFlash feature. I basically created a new route and sent the flash message.
app.post('/login',
passport.authenticate('local', {failureRedirect: "/loginfailed"}),
function(req, res) {
if (!req.user.isActive){
req.flash("success","Your account needs to be verified. Please check your email to verify your account");
req.logout();
res.redirect("back")
}else{
res.redirect("/");
}
});
//Route to login page if user failed to login. I created this to allow flash messages and not interfere with regular login route
app.get("/loginfailed", function(req, res){
if (!req.user){
req.flash("success", "Username or password is incorrect.");
res.redirect("/login");
}
});
I had the same problem and I solved it.
Your success message and failure message variables have to match with whatever the passport JS is using. So after playing around, I realize that passport JS is using the variable success to display success flash and error to display failure flash.
So first, you can create a super global variable like this in your app.js:
app.use(function(req, res, next) {
res.locals.error = req.flash("error");
res.locals.success = req.flash("success");
next();
});
Then use those variables in your temple. I am using ejs so it looks like this:
<%if(error && error.length > 0){%>
<div class="alert alert-danger"><%=error%></div>
<%}%>
<%if(success && success.length > 0){%>
<div class="alert alert-success"><%=success%></div>
<%}%>
And finally your passport JS code should be like this:
router.post("/login",passport.authenticate("local", {
successFlash : "Hey, Welcome back",
successRedirect : "/mountains",
failureFlash : true,
failureRedirect :"/login"
}), function(req, res){
});
My solution
app.js code:
const flash = require('connect-flash');
app.use(flash());
require('./src/config/passport.js')(app);
local.strategy.js code
const passport = require('passport');
const { Strategy } = require('passport-local');
const userModel = require('./../../../models/userModel');
module.exports = function localStrategy() {
passport.use(new Strategy(
{
usernameField: "username",
passwordField: "password"
}, (username, password, done) => {
userModel.findOne({ username }, (err, user) => {
if (err) {
res.send(err);
}
if (user && (user.username == username && user.password == password)) {
done(null, user, { message: "Success" });
} else {
done(null, false, { message: "Invalid credentials!" });
}
});
}
));
}
authController.js code
function signIn(req, res) {
res.render('signin', {
nav,
title: "Sign in",
message: req.flash()
});
};
authRouter.js code
authRouter.route('/signin').get(signIn).post(passport.authenticate('local', {
successRedirect: '/admin',
failureRedirect: '/auth/signin',
failureFlash: true
}));
signin.js template code (my view engine is ejs)
<% if (message) { %>
<p style="color: red;" class="text-center"><%= message.error %></p>
<% } %>
When fields required for authentication are missing, passport.authenticate will not trigger the Strategy callback as OP points out.
This has to be handled inside the custom callback (scroll down page) in the authenticate function by using the info parameter.
In case of the OP's code like so:
app.post('/login', function (req, res, next) {
passport.authenticate('local-login',
{
successRedirect: '/profile',
failureRedirect: '/login',
failureFlash: true,
},
function (error, user, info) {
//This will print: 'Missing credentials'
console.log(info.message);
//So then OP could do something like
req.flash(info.message);
//or in my case I didn't use flash and just did
if (info)
res.status(400).send(info.message);
...
})(req, res, next);
});
I know this question is old but I have stumbled upon this issue myself and I see that there is still no accepted answer. Furthermore I think that all the answers misinterpreted what the OP was actually asking for - a way to access the badRequestMessage.
PassportJS docs are not very helpful either:
If authentication failed, user will be set to false. If an exception occurred, err will be set. An optional info argument will be passed, containing additional details provided by the strategy's verify callback.
What this actually means is that info parameter can be passed as a third parameter from your strategy like so: done(error,user,info), but fails to mention that this parameter is used by default in case of missing credentials. Overall I think PassportJS docs could do with some overhaul as they lack detail and link to non-existent examples.
This answer has helped me understand that the missing credentials message is passed in the info parameter.

Categories