I'm using Passport with the passport-jwt strategy to authenticate users with a JavaScript Work Token. I need to be able to authorise users based on some metadata so I've set up a custom callback which I'm attaching to the route.
router.get('/', auth.loginRequired, function (req, res) {...
but I'm having problems calling the function. I've massively simplified it and the strategy as shown below:
module.exports = {
loginRequired: function (req, res, next) {
passport.authenticate('jwt', {session: false}, function(err, user, info) {
if (!err) {
next()
} else {
res.status(401).send
}
})(req, res, next)
}
}
The strategy is shown below:
passport.use(new JwtStrategy(opts, function(payload, done) {
var user = {firstName: 'Geraint', email: 'test.user#test.co.uk'}
if (payload) {
done(null, user)
} else {
done('Error', null)
}
}))
When the JWT is valid, the passport.authenticate callback is being called as expected with null and the user being passed in correctly. When the JWT is invalid though, the error is being passed into the authenticate callback as info. err is null and user is false.
Why are the parameters getting jumbled if there's an error?
I also worked with this and i got the same output. Passport js work in this way only.
You can change condition and it will work
module.exports = {
loginRequired: function (req, res, next) {
passport.authenticate('jwt', {session: false}, function(err, user, info) {
if (user) {
next()
} else {
res.status(401).send
}
})(req, res, next)
}
}
In case of user object present, it will return success otherwise it will return error.
What I do for JWT authentication is:
router.get('/', passport.authenticate('jwt', {session: false}), function (req, res) {...
If the JWT is not valid, then it returns a 401. If it is valid, then it continues into my route.
Related
The passport docs use this to protect a route:
app.get('/api/me',
passport.authenticate('basic', { session: false }),
function(req, res) {
res.json(req.user);
});
How does authenticate() know which request to authenticate? I don't pass the request to it.
The passport.authenticate returns a function.
You can try this
console.log(passport.authenticate('basic', { session: false }));
and it will print something like
function(req, res, next){ ... }
This means that the app.get will look something like this after your app starts
app.get('/api/me',
function(req, res, next){
// now passport has access to the "req" even though you didn't pass request to it
// passport's authentication logic here
},
function(req, res) {
res.json(req.user);
});
I am setting up a nodejs project with passportjs and passport-jwt. I see where you can specify passport.authenticate for each route you want to secure. However, I do not see a way to lock down all router except maybe login and register. I see where express-jwt allows for the use of express-unless, which seems to accomplish this functionality. Is there a similar mechanism for passport-jwt and if so how would this be accomplished?
Actually you don't even need express-unless you can use the fact that express allow to register middlewares that get executed all the time to do your filtering
const express = require('express');
const app = express();
function authenticateSomeRoutesMiddleware(req, res, next) {
if (/(login|register)/.test(req.originalUrl)) {
// No authentication needed
return next();
} else {
// Option 1 => use default passport logic
// which respond with a 401 unauthorized status if authentication fails
passport.authenticate('jwt', { session: false}), function(req, res, next) {
// Do something now you know that the user has been authenticated
return next(); // this will call the next middleware on the stack
})(req, res, next);
// Option 2: use a custom callback to allow your application
// to handle success or failure
// As per passport spec:
// - 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.
passport.authenticate('local', function(err, user, info) {
if (err) {
// Error in authentication process; handle it or call...
return next(err);
}
if (!user) {
// Authentication failed (based on your strategy's implementation)
// You can for example try again
return res.redirect('/login');
}
// If you are using session to store the user call req.logIn() else call `return next()` directly
req.logIn(user, function(err) {
if (err) { return next(err); }
return next();
});
})(req, res, next);
}
}
// add this BEFORE your route definitions
app.use(authenticateSomeRoutesMiddleware);
// add all your routes here
app.use('/login', function(req, res, next) {
// do something
});
app.use('/register', function(req, res, next) {
// do something else
});
app.use('/some/protected/route', function(req, res, next) {
// this will get called once the authentication process has been cleared
});
//...
so my route (for '/dash') looks like this:
// validating using JWT
router.post('/dash', passport.authenticate('jwt', {session: false}), function (req, res) {
res.json({'success': true});
});
// validating using LOCAL
router.post('/dash', authenticationHelpers.isAuth, function (req, res) {
res.json({'success': true});
});
// authenticationHelpers.isAuth
function isAuth(req, res, next) {
if (req.isAuthenticated())
return next();
res.status(401).json({"authenticated": false});
}
So, how do I use both Local & JWT Strategy on same app (on same route) ? How do I combine them both.
Note: Local for web app, JWT for mobile app
Finally figured it out.
Modified isAuth function:
function isAuth(req, res, next) {
if (req.headers.authorization) {
passport.authenticate('jwt', {session: false}, function (err, user, info) {
if ((!err || !info) && user) {
req.user = user;
return next();
}
res.status(401).json({authenticated: false, message: "Login expired."});
})(req, res, next);
} else {
if (req.isAuthenticated())
return next();
res.status(401).json({authenticated: false});
}
}
Suggestions are welcomed...
js and Passport.js. So I have this :
router.post('/login', middleware1, middleware2);
I also have this on my app.js
passport.use(new passportLocal(
{ usernameField: 'email' },
function(email, password, done) {
// working logic here
// this returns a user object
// to the middleware1 if query is ok
// returns error otherwise.
}
));
And here are my middlewares
middleware1 = function (req, res, next) {
passport.authenticate('local', function(error, user, message){
// How do I get the 'user' object and pass it
// to the outer req or res?
req.user = user; // <- is this ok?
})(req, res, next);
req.user = user;
next();
}
middleware2 = function (req, res, next) {
console.log("user ", req.user); // <- this will obviously print null or undefined
}
Now. How do I pass the user object from inside the passport.authenticate() to middleware2. I'm thinking of using promise something like this passport.authenticate().then() but I dont know the syntax. Please help. Thanks.
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);
});
authenticate()'s function signature is standard Connect middleware, which makes it convenient to use as route middleware
Why are you wrapping the passport.authenticate() in another function?
I'm not sure what you are trying to do, but I would probably start without the misdirection
router.post('/login', passport.authenticate('local'), middleware2(req,res,next));
function middleware2(req,res,next) {
console.log("user ", req.user);
}
If you want the req object passed to your function set the passport js options 'passReqToCallback: true '
I have the following working code to authenticate through the passport-local strategy:
app.post('/api/login', passport.authenticate('local-login', {
successRedirect : '/api/login/success',
failureRedirect : '/api/login/error',
failureFlash : true
}));
app.get('/api/login/error', function(req, res) {
res.send(401, {error: req.flash('loginMessage')});
});
app.get('/api/login/success', function(req, res) {
res.send(200, {user: req.user});
});
However, ideally I want to handle the errors and success messages from one express route, and not redirect to two extra routes.
Is this possible? I tried using a 'custom callback' but that seemed to error out on serializing users for some reason.
You can use custom callback, such as:
passport.authenticate('local', function (err, account) {
req.logIn(account, function() {
res.status(err ? 500 : 200).send(err ? err : account);
});
})(this.req, this.res, this.next);
In err object you can find all needed errors, which was appeared at authentication.
Are you using Mongoose?
Try adding this to your server.js/index.js
var User = mongoose.model('User');
passport.use(new LocalStrategy(User.authenticate()));
passport.use(User.createStrategy());
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
This to your routes index.js
var auth = require('./auth');
app.post('/api/auth/login', passport.authenticate('local'),auth.login);
auth.js:
var UserModel = require('../models/user');
var User = new UserModel();
exports.login = function(req, res) {
var user = req.user;
req.login(user, function(err) {
//if error: do something
return res.status(200).json(user)
});
};
Add this to model index.js
var passportLocalMongoose = require('passport-local-mongoose');
userSchema.plugin(passportLocalMongoose, {
usernameField: 'email',
usernameLowerCase: 'true'
});
I'm making a lot of assumptions on structure and packages here. but this should work
EDIT
For custom callbacks:
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);
});
Here you instead of res.redirect you can use something like return res.status(404).json("Not Found)
See docs for more information : http://passportjs.org/guide/authenticate/