Scope access passport-jwt - javascript

Can someone explain to me in detail why the route of /profile has access to the user object. I'm currently learning JavaScript and NodeJS your answer will be a big help in my learning Thank you guys.
app.post('/login',function (req, res) {
let email = req.body.email;
let password = req.body.password;
User.getUserByEmail(email, (err, user) => {
if (err) throw err;
if (!user) {
return res.json({
success: false,
message: "User not found!"
});
}
User.comparePassword(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
var token = jwt.sign(user.toJSON(), config.JWT_SECRET, {
expiresIn: '15m'
});
res.json({
success: true,
token: token,
user: {
id: user._id,
email: user.email
}
});
} else {
return res.json({
success: false,
message: "Password incorrect!"
});
}
})
});
});
app.get('/profile', passport.authenticate('jwt', {
session: false
}), (req, res) => {
res.json({user: req.user});
});

It is because your passport.authenticate() call populates user to req.
From passports.org:
app.post('/login',
passport.authenticate('local'),
function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
res.redirect('/users/' + req.user.username);
});
It is the same for your route, except your path and authentication method is different.
See the documentation for more info: http://www.passportjs.org/docs/authenticate/

Some background
The function app.get takes an url and one or many callbacks with (req, res, next) => {} as their signature
The callbacks are executed one after the other. In anyone of these callbacks you can modify the req object and it will "propagate" to the next callbacks
To switch from a callback to the next one, you call next
In your case
The call to passport.authenticate('jwt', {sessions: false}) returns a callback, that's executed before you send the json response.
That callback itself athenticates the user, then "inject" its value into the req object.
As I mentioned before, this req will "propagate" to the next callback. And that's why when you send your json response, it req already contains the user key

Related

node js callback function issue

i am new to developing apis in node js. recently i started working on a node js application there i use jwt tokens to authentication purposes.
my jwt validation function is as bellow
var jwt = require('jsonwebtoken');
var config = require('../config.js')
var JwtValidations = {
//will validate the JTW token
JwtValidation: function(req, res, next, callback) {
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, config.secret, callback);
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
}
}
module.exports = JwtValidations;
to this function i am passing a call back function so that if the jtw token validation passed i can serve to the request. bellow is one example of adding a user to the system
// addmin a user to the database
router.post('/add', function(req, res, next) {
JwtValidations.JwtValidation(req, res, next, function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
} else {
retrunval = User.addUser(req.body);
if (retrunval === true) {
res.json({ message: "_successful", body: true });
} else {
res.json({ message: "_successful", body: false });
}
}
})
});
// addmin a user to the database
router.put('/edit', function(req, res, next) {
JwtValidations.JwtValidation(req, res, next, function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
} else {
User.UpdateUser(req.body, function(err, rows) {
if (err) {
res.json({ message: "_err", body: err });
} else {
res.json({ message: "_successful", body: rows });
}
});
}
})
});
as you can see in both of these function i am repeating same code segment
return res.json({ success: false, message: 'Failed to authenticate token.' });
how do i avoid that and call the callback function if and only if JwtValidations.JwtValidation does not consists any error
how do i avoid that and call the callback function if and only if JwtValidations.JwtValidation does not consists any error
Just handle it at a level above the callback, either in JwtValidations.JwtValidation itself or a wrapper you put around the callback.
If you were doing it in JwtValidations.JwtValidation itself, you'd do this where you call the callback:
if (token) {
// verifies secret and checks exp
jwt.verify(token, config.secret, function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
}
callback(decoded);
});
} else /* ... */
Now when you use it, you know either you'll get the callback with a successfully-decoded token, or you won't get a callback at all but an error response will have been sent for you:
router.put('/edit', function(req, res, next) {
JwtValidations.JwtValidation(req, res, next, function(decoded) {
User.UpdateUser(req.body, function(err, rows) {
if (err) {
res.json({ message: "_err", body: err });
} else {
res.json({ message: "_successful", body: rows });
}
});
})
});
The code above is using a lot of (old-style) NodeJS callbacks. That's absolutely fine, but you may find it's simpler to compose bits of code if you use promises instead. One of the useful things do is split the return path in two, one for normal resolution, one for errors (rejection).
Use the jwt authentication function as a middleware function and not as a route, plenty of examples on the express documentation.
http://expressjs.com/en/guide/using-middleware.html

Prorogate a custom error in PassportJS

I'm trying to refactor my existing app in order to add support for PassportJS, but it's getting more difficult than expected.
I'm using passport-jwt as strategy. So I have
passport.use(new JwtStrategy(options, user.verify));
router.post(
'/login/jwt',
passport.authenticate('jwt', {session: false, failWithError: true})
);
And if user.verify fails it calls (for example)
done(new Error(errors.BAD_REQUEST));
But I have no way to handle this error, whatever I pass as first parameter of the done callback, Passport always sends a 401 - Unauthorized response.
This is not what I expect since I have many error handlers in my codebase and I want to communicate a meaningful error to the client.
I googled a lot so far, and I opened several SO questions besides the official documentation, but any of those solutions fixes my problem.
For example, a common solution for this problem is using a closure in order to access req and res objects (as the link above), but this is not applicable to my existing app.
Can someone help me?
So I assume you want to help the user and say the password is incorrect for example.
In the 'Verify Callback' Section you can find this example:
return done(null, false, { message: 'Incorrect password.' });
And by default, if authentication fails, Passport will respond with a 401 Unauthorized status
To catch this message you could try something like this:
http://passportjs.org/docs#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);
});
Does this help?
Edit: With no IIFE
app.post('/login',
passport.authenticate('local', { failWithError: true }),
function(req, res, next) {
// Handle success
return res.send({ success: true, message: 'Logged in' })
},
function(err, req, res, next) {
// Handle error
return res.status(401).send({ success: false, message: err })
}
)

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