How done function works in passport js? - javascript

I have auth function, I want it to authenticate my user route.
// auth.js
function auth(request, response, next) {
passport.authenticate('jwt', { session: false }, async (error, token) => {
if (error || !token) {
response.status(401).json({ message: 'Unauthorized' });
}
next(token);
})(request, response, next);
next()
}
module.exports = auth;
And heres my jwt strategy
// passport.js
passport.use(
new JwtStrategy(opts, (payload, done) => {
console.log('payload', payload) // this works
User.findById(payload.id)
.then(user => {
if (user) {
console.log('here user', user) // this also works
return done(null, user);
}
return done(null, false);
})
})
);
But why when I console log my request It doesn't show me the user that I already declare in done(null, user)
const auth = require('../auth.js')
router.get('/', auth, async (req, res) => {
console.log(req.user) // return undefined
// other code
});

There are a couple issues that I can see:
From your auth() middleware function, your are calling next() before passport has had a chance to authenticate the incoming request - which happens asynchronously. You should remove the synchronous call to next() there, and defer to passport.authenticate() callback to handle this.
In passport.authenticate() callback, you're calling next() with an argument - express will take this as an error occurring and jump to the next error middleware in line.
Edit: I also checked the signature of the passport.authenticate() callback and it seems to be (error, user, info) - not (error, token).
Edit 2: It also seems like when passing passport.authenticate() a custom callback, it becomes your responsability to expose user on the req object by calling passport req.login() function. Please take a look here:
http://www.passportjs.org/docs/authenticate/ (custom callback section at the end)
http://www.passportjs.org/docs/login/

Related

passing variable to next middleware in req in express.js

I am trying to pass variable to the next middlewares through the req object.
getting some data from database and passing that data to request for next middlewares to use.
User.findone({ _id: someId })
.then(user => { req.user = user })
.catch(err => { })
After that then and catch block i am using next().
Therefore for the next middlewares i am getting req.user undefined.
but if i pass the next() function in the then block after
req.user = user like .then(user=> {req.user = user; next()}) than i am getting req.user a valid user object to use for the next middlewares.
what is the reason for this behaviour??
That's because the User.findOne function is asynchronous. The result of that function is only known in the then block.
const middleware = (req, res, next) => {
User.findOne({ _id: someId })
.then(user => {
req.user = user;
})
.catch(err => { });
next(); // If you put next() here, the request will go straight to the next middleware without waiting for User.findOne() to complete.
};
const middleware = (req, res, next) => {
User.findOne({ _id: someId })
.then(user => {
req.user = user;
next(); // Putting next() here is correct
})
.catch(err => {
next(err); // Remember to handle error to avoid hanging the request
});
};
then... is called after the User.findone promise resolves. Thus, if you put next() outside of then, it will be called before then.
You could read more details at promise-basics
Alternatively try to use async-await as it looks more straightforward.

knowing better authentication with Passport / JwtStrategy

On one working project I downloaded from internet...
In one location of the code I have the following:
passport.use(new JwtStrategy({
secretOrKey: credentials.secret,
jwtFromRequest: ExtractJwt.fromAuthHeader(),
},
function(payload, done) {
User.findById(
payload._id,
function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
}
}
);
}
));
In other location of the code I have the following:
var requireAuth = passport.authenticate('jwt', { session: false });
//...
module.exports = function(app) {
//...
authRoutes.get('/protected', requireAuth, function(req, res) {
res.send({ content: 'Success' });
});
//...
}
I have 2 questions here:
1- What about if instead doing: return done(err, false); we do: done(err, false); without return?
2- Is the 3rd argument (that middleware function) in the call of: authRoutes.get(*, *, *) always reached no matter what's going on inside the function: function(payload, done){} (second argument on: new JwtStrategy(*, *)? Notice that middleware function (that 3rd argument) returns a Success response. What about if something goes wrong inside the JWT authentication process?
That's fine. Both cases will result in undefined being returned anyways.
Middleware is executed in the order in which they are defined. So requireAuth will always execute first and then function(req, res){}. But if requireAuth fails for whatever reason, function(req, res){} will be skipped in the middleware stack. Any errors should be handled in error middleware. If you do not handle them, then the whole application will crash.

Invoke route handlers on both, Successful Authentication and failure, for passportjs + Node.js + Express.js Rest API

Please take a look at this basic login/token process using passport basic strategy for a Rest API:
the route:
router.get('/token', authenticate.basic, controller.token);
authenticate basic strategy:
authenticate.basic = passport.authenticate('basic', {session: false});
/* implementation of the Basic Strategy*/
passport.use('basic', new BasicStrategy(function(username, password, done) {
authenticationService.login(username, password).then(function(user) {
if (!user) {
return done(null, false, { message: 'Login failed' });
}
return done(null, user);
}).catch(function(e) {
return done(e)
});
}));
token controller (route handler):
controller.token = function(req, res, next) {
if (!req.user) {
// TODO fix this dead branch
return res.json(401, {error: "Login failed"});
}
authService.issueToken(req.user).then(function(token) {
var user = {
user_id: req.user.id,
access_token: token
}
return res.json(user);
}).catch(function(e) {
return next(e);
});
};
As mentioned in the documentation :
By default, if authentication fails, Passport will respond with a 401
Unauthorized status, and any additional route handlers will not be
invoked. If authentication succeeds, the next handler will be invoked
and the req.user property will be set to the authenticated user.
Is there a way to bypass this behavior and invoke the route handler even if the authentication fails ?
You're looking for Passport's "Custom callback" feature.
Basically, you need to give the authenticate method a third argument to override the default behavior. This implies that the application becomes responsible for logging in the user, which is simply a matter of calling the req.login() method.
authenticate.basic = function (req, res, next) {
passport.authenticate('basic', {
session: false
}, function(err, user, info) {
if (err) {
// Authentication failed, you can look at the "info" object
return next(err);
}
if (!user) {
// The user is not logged in (no token or cookie)
return res.redirect('/login');
}
req.login(user, function(err) {
if (err) {
// Something wrong happened while logging in, look at the err object
return next(err);
}
// Everything's good!
return res.redirect('/users/' + user.username);
});
})(req, res, next);
}

Passport.js is not passing user to request in req.login()

My passport.js configuration goes like so:
const Local = require("passport-local").Strategy;
const USMODEL = require("../models/user.js");
passport.serializeUser(function(user, done) {
console.log("SERIALIZING USER");
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log("DESUSER", id);
var US = mongoose.model("RegUser", USMODEL);
US.findById(id, function(err, user) {
done(err, id);
});
});
passport.use("local-login", new Local({
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},function(req, email, password, done) {
var US = mongoose.model("RegUser", USMODEL);
US.findOne({"email": email}, function(err, user){
if(err) throw err;
if(!user) return done(null, false);
if(!user.validPassword(password)) {
console.log("password not valid");
return done(null, false);
}
return done(null, user);
});
}));
I'm changing the mongoose model within each function because I juggle with multiple collections at a time and I like to have complete control of what's going on.
My router.js file has the following paths that make use of the passport middleware:
app.get("/user/login", function(req, res) {
res.render("signin");
});
app.post('/user/login', function (req, res){
passport.authenticate('local-login', function(err, user, info){
if (err) return res.redirect("/");
if (!user) return res.redirect('/');
else {
req.login(user, function(err) {
if (err) return next(err);
console.log("Request Login supossedly successful.");
return res.redirect('/admin/filter');
});
}
})(req, res);
});
Which, upon successful authentication, redirects to /admin/filter in the same router that goes like so.
app.get("/admin/filter", isLoggedIn, function(req, res){
//rendering stuff here
});
Now, the admin/filter request goes past a middleware called isLoggedIn which, in theory protects my endpoints. It goes like so:
function isLoggedIn(req, res, next) {
console.log("This is the authentication middleware, is req authenticated?");
console.log(req.isAuthenticated());
console.log("Does req.user exist?")
console.log(req.user);
return next();
}
Now, you would expect that because I called req.login and I got redirected to my endpoint of choice, the request would be authenticated. This is not the case.
Request Login supossedly successful.
This is the authentication middleware, is req authenticated?
false
Does req.user exist?
undefined
I can't seem to find the source of my problem. Everything checks out, as the strategy is being invoked, as well as the callback function and req.login which would render, in theory, a req.user object with data in it. One odd thing I've observed is that I don't see the passport.deserializeUser() method in action. Ever. But that could be tangential to the problem. Passport is definitely using my strategy and rendering a user object, but somehow this same object is not going into the request. Do you have any suggestion or idea about what is going on?
I solved the problem by juggling around with the tutorial I started with when I first learned how to use the Passport middleware. Turns out I was doing the configuring wrong: My code used to be like this in the server file:
pass = require("passport");
app.use(pass.initialize());
app.use(pass.session());
require("./app/config/passport.js")(pass);
when it should have been this:
pass = require("passport");
require("./app/config/passport.js")(pass);
app.use(pass.initialize());
app.use(pass.session());
Either I missed the part in the documentation where it's specified that configuration must come before initialization or it's simply written off as a trivial thing to remark. Either way, I solved my problem.
Make sure withCredentials: true while sending the post request.
// register
axios.post(uri, {
email: email,
password: password,
confirmPassword: confirmPassword
}, {
withCredentials: true
})

Sending back a JSON response when failing Passport.js authentication

I'm using Node.js as a backend API server for an iPhone client. I'm using Passport.js to authenticate with a local strategy. The relevant code is below:
// This is in user.js, my user model
UserSchema.static('authenticate', function(username, password, callback) {
this.findOne({ username: username }, function(err, user) {
if (err){
console.log('findOne error occurred');
return callback(err);
}
if (!user){
return callback(null, false);
}
user.verifyPassword(password, function(err, passwordCorrect){
if (err){
console.log('verifyPassword error occurred');
return callback(err);
}
if (!passwordCorrect){
console.log('Wrong password');
return callback(err, false);
}
console.log('User Found, returning user');
return callback(null, user);
});
});
});
and
// This is in app.js
app.get('/loginfail', function(req, res){
res.json(403, {message: 'Invalid username/password'});
});
app.post('/login',
passport.authenticate('local', { failureRedirect: '/loginfail', failureFlash: false }),
function(req, res) {
res.redirect('/');
});
Right now, I have managed to redirect a failed login to /loginfail, where I send back some JSON to the iPhone client. However, this doesn't have enough granularity. I want to be able to send back the appropriate errors to the iPhone client, such as: "No user found" or "Password is wrong". With my existing code, I don't see how this can be accomplished.
I tried to follow the examples for a custom callback on the passport.js site, but I just can't get it to work due to lack of node understanding. How could I modify my code so that I'd be able to send back a res.json with an appropriate error code/message?
I am trying something like this now:
// In app.js
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
console.log(info);
// *** Display message without using flash option
// re-render the login form with a message
return res.redirect('/login');
}
console.log('got user');
return res.json(200, {user_id: user._id});
})(req, res, next);
});
// In user.js
UserSchema.static('authenticate', function(username, password, callback) {
this.findOne({ username: username }, function(err, user) {
if (err){
console.log('findOne error occurred');
return callback(err);
}
if (!user){
return callback(null, false);
}
user.verifyPassword(password, function(err, passwordCorrect){
if (err){
return callback(err);
}
if (!passwordCorrect){
return callback(err, false, {message: 'bad password'});
}
console.log('User Found, returning user');
return callback(null, user);
});
});
});
But back when I try to console.log(info), it just says undefined. I don't know how to get this custom callback working...Any help would be appreciated!
I had a similar issue with Passport and failed login responses. I was building an API, and wanted all responses to be returned as JSON. Passport responds to an invalid password with status: 401 and body: Unauthorized. That's just a text string in the body, not JSON, so it broke my client which expected all JSON.
As it turns out, there is a way to make Passport just return the error to the framework instead of trying to send a response itself.
The answer is to set failWithError in the options passed to authenticate:
https://github.com/jaredhanson/passport/issues/126#issuecomment-32333163
From jaredhanson's comment in the issue:
app.post('/login',
passport.authenticate('local', { failWithError: true }),
function(req, res, next) {
// handle success
if (req.xhr) { return res.json({ id: req.user.id }); }
return res.redirect('/');
},
function(err, req, res, next) {
// handle error
if (req.xhr) { return res.json(err); }
return res.redirect('/login');
}
);
This will invoke the error handler after Passport calls next(err). For my app, I wrote a generic error handler specific to my use case of just providing a JSON error:
// Middleware error handler for json response
function handleError(err,req,res,next){
var output = {
error: {
name: err.name,
message: err.message,
text: err.toString()
}
};
var statusCode = err.status || 500;
res.status(statusCode).json(output);
}
Then I used it for all api routes:
var api = express.Router();
...
//set up some routes here, attached to api
...
// error handling middleware last
api.use( [
handleError
] );
I didn't find the failWithError option in the documentation. I stumbled upon it while tracing through the code in the debugger.
Also, before I figured this out, I tried the "custom callback" mentioned in the #Kevin_Dente answer, but it didn't work for me. I'm not sure if that was for an older version of Passport or if I was just doing it wrong.
I believe the callback function that your 'authenticate' static calls (called 'callback' in your code) accepts a 3rd parameter - "info" - which your code can provide. Then, instead of passing in the { failureRedirect: ...} object, pass in a function which takes 3 arguments - err, user, and info. The "info" you provided in your authenticate method will be passed to this callback.
Passport calls this scenario "custom callback". See the docs here:
http://passportjs.org/guide/authenticate/
There is an official documentation for Custom Callback:
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);
});
https://github.com/passport/www.passportjs.org/blob/master/views/docs/authenticate.md
As per the official documentation of Passport you may use custom callback function to handle the case of failed authorization and override the default message.
If you are developing REST API and then you would want to send out pretty JSON response something as below:
{
"error": {
"name": "JsonWebTokenError",
"message": "invalid signature"
},
"message": "You are not authorized to access this protected resource",
"statusCode": 401,
"data": [],
"success": false
}
I was using Passport JWT authentication to secure some of my routes and was applied the authMiddleware as below:
app/middlewares/authMiddleware.js
const express = require('express');
const router = express.Router();
const passport = require('passport');
const _ = require('lodash');
router.all('*', function (req, res, next) {
passport.authenticate('local', function(err, user, info) {
// If authentication failed, `user` will be set to false. If an exception occurred, `err` will be set.
if (err || !user || _.isEmpty(user)) {
// PASS THE ERROR OBJECT TO THE NEXT ROUTE i.e THE APP'S COMMON ERROR HANDLING MIDDLEWARE
return next(info);
} else {
return next();
}
})(req, res, next);
});
module.exports = router;
app/routes/approutes.js
const authMiddleware = require('../middlewares/authMiddleware');
module.exports = function (app) {
// secure the route by applying authentication middleware
app.use('/users', authMiddleware);
.....
...
..
// ERROR-HANDLING MIDDLEWARE FOR SENDING ERROR RESPONSES TO MAINTAIN A CONSISTENT FORMAT
app.use((err, req, res, next) => {
let responseStatusCode = 500;
let responseObj = {
success: false,
data: [],
error: err,
message: 'There was some internal server error',
};
// IF THERE WAS SOME ERROR THROWN BY PREVIOUS REQUEST
if (!_.isNil(err)) {
// IF THE ERROR IS REALTED TO JWT AUTHENTICATE, SET STATUS CODE TO 401 AND SET A CUSTOM MESSAGE FOR UNAUTHORIZED
if (err.name === 'JsonWebTokenError') {
responseStatusCode = 401;
responseObj.message = 'You are not authorized to access this protected resource';
}
}
if (!res.headersSent) {
res.status(responseStatusCode).json(responseObj);
}
});
};
You can do that without custom callbacks using property passReqToCallback in your strategy definition:
passport.use(new LocalStrategy({passReqToCallback: true}, validateUserPassword));
Then you can add your custom auth error code to the request in your strategy code:
var validateUserPassword = function (req, username, password, done) {
userService.findUser(username)
.then(user => {
if (!user) {
req.authError = "UserNotFound";
return done(null, false);
}
And finally you can handle these custom errors in your route:
app.post('/login', passport.authenticate('local', { failWithError: true })
function (req, res) {
....
}, function(err, req, res, next) {
if(req.autherror) {
res.status(401).send(req.autherror)
} else {
....
}
}
);
A short workaround is to emulate the Flash method call which intended originally to support connect-flash and to use this method to return the JSON object.
first define the "emulator":
var emulateFlash = function (req, res, next) {
req.flash = (type, message) => {
return res.status(403).send({ status: "fail", message });
}
next();
}
this will inject the flash method which will send the error JSON object upon failure.
In the route do the following:
1st, use the emulator across the board using:
router.use(emulateFlash);
One can instead use the emulateFlash method on each route needed.
2nd, on the route when using authenticate, specify the failureFlash option using a message:
router.route("/signin")
.post(.authenticate('local', { session: false, failureFlash: "Invalid email or password."}), UsersController.signIn);
I tested this for both failed authentication as well as successful and found it working. Looking at the code I could not find any other way to return an object other than implementing the callback method which requires much more work.

Categories