How can I combine passport-local to return a JWT token on successful authentication?
I want to use node-jwt-simple and looking at passport.js I am not sure how to go about.
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
Is it possible to return the token when calling done() ?
Something like this... (just pseudo code)
if(User.validCredentials(username, password)) {
var token = jwt.encode({username: username}, tokenSecret);
done(null, {token : token}); //is this possible?
}
If not, how can I return the token?
I figured it out!
First of all you need to implement the correct strategy. In my case LocalStrategy, and you need to provide your validation logic. For example sake let's use the one in passport-local.
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
the verify call back you provide function(username, password, done) will take care of finding your user and checking if the password matches (beyond the scope of the question and my answer)
passport.js expects several pieces for it to work, one is that you return the user in the strategy. I was trying to change that part of the code, and that was wrong. The callback expects false if the validation fails and an object (the validated user) if you are successful.
Now.... how to integrate JWT?
In your login route you will have to handle a successful auth or an unsuccessful one. And it is here that you need to add the JWT token creation. Like so:
(remember to disable the session, otherwise you will have to implement the serialize and deserialize functions. And you don't need those if you are not persisting the session, which you are not if you are using a token based auth)
From passport-local examples: (with the JWT token added)
// POST /login
// This is an alternative implementation that uses a custom callback to
// achieve the same functionality.
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
return res.json(401, { error: 'message' });
}
//user has authenticated correctly thus we create a JWT token
var token = jwt.encode({ username: 'somedata'}, tokenSecret);
res.json({ token : token });
})(req, res, next);
});
And that is it! Now when you call /login and POST username and password (which should always be over SSL) the first code snippet above will try to find a user based on the username you provided and then check that the password matches (Of course you will need to change that to suit your needs).
After that your login route will be called and there you can take care of returning an error or a valid token.
Hope this will help someone. And if I have made any mistakes or forgot something let me know.
This is a great solution, I just want to add this:
var expressJwt = require('express-jwt');
app.use('/api', expressJwt({secret: secret}));
I like to use "express-jwt" to validate the token.
btw: this article is great to learn how to handle the token in the client side, using Angular, in order to send it back with every request
https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/
Here's a boiler-plate I'm working on for specifically using api tokens only (no sessions...not that session are bad of course; just we're using token approach):
https://github.com/roblevintennis/passport-api-tokens
Related
I am using JWT working with passport.js for authentication. I have a local strategy that signs up and another that logs in a user. Currently, I have it return the token by doing:
req.login(user, {session: false}, (err) => {
if (err) {
res.send(err);
}
var jwtUser = {
_id: user.id,
email: user.email
};
const token = jwt.sign(jwtUser, config.passport.jwtSecret);
return res.json({jwtUser, token});
});
This returns an object like:
{"jwtUser":
{
"_id":"5c55f0be9ddcf71a704d92b2",
"email":"john.doe#example.com"
},
"token":"<token>" // redacted this because it contains my personal email
}
So I am getting a token back that is correct (I've checked using an online decoder, it gives the correct user ID and the correct email).
When I use this route to try and test whether the JWT strategy is working or not I get a 401 unauthorized access message.
Here is my JWT strategy
var opts = {};
opts.jwtFromRequest = ExtractJWT.fromAuthHeaderAsBearerToken();
opts.secretOrKey = config.passport.jwtSecret;
passport.use('jwt', new JWTstrategy(opts, function(jwt_payload, done) {
console.log('payload received: ', jwt_payload);
User.findOne({id: jwt_payload.id}, function(err, user) {
console.log('User: ', user);
if (user) {
console.log(user);
return done(null, user);
}
if (err) {
return done(err, false);
}
});
}));
My route
router.get('/auth/test', passport.authenticate('jwt', { session: false }), (req, res) => {
res.send('You reached a private route.');
});
I have my local strategy working using the same method of calling passport.
I think I have included everything of value, if something is missing please say and I will update the OP with it.
I should note I am using postman to test this and using the authorization method of bearer token.
Thanks in advance!
I am trying to use passportjs in my nodejs app with express4. For testing purpose, I am using session-file-store to keep session.
The way I set up the session and passportjs :
this.app.use(session({
secret: process.env.SESSION_SECRET || configuration.session_secret,
store: new fileStore(),
resave: false,
saveUninitialized: false
}));
// config authentication
this.app.use(passport.initialize());
this.app.use(passport.session());
// Configure Passport
passport.use('local-login', new localStrategy.Strategy(
(username, password, done) => {
Services.users.authenticatePromise(username, password).then(
function(token) {
done(null, token);
},
function(err) {
done(null, false, err);
},
).done();
},
));
// Serialize user in session
passport.serializeUser((token: AccessToken, done) => {
let user = {
_token: token.accessToken,
_expiryInAt: token.expiryInMs + new Date().getTime(),
};
done(null, user);
});
passport.deserializeUser((sessionUser, done) => {
done(null, sessionUser);
});
However, the issue I am having is that if I don't write anything into the session before user is logged in, passportjs works fine. However if I am trying to write some data into session like shopping cart details, then passportjs would not be able to serialize the user token into the session store any more. so login cannot be successful.
What am I missing?
you have a couple of things wrong with your code that I can see.
first, serializeUser should take an object and return a string (typically a unique id or database key). Your function currently takes a string and returns an object; that's backwards.
second, deserializeUser should take a string and return a user object. Your function currently takes an object and returns that same object.
finally ,serializeUser and deserializeUser must be inverses of each other; meaning, passport can call deserializeUser(serializeUser(user)) and get back the original user object.
So you're going to want something like this:
passport.serializeUser((user, done) => {
return user.id;
});
passport.deserializeUser((id, done) => {
getUserFromDatabase({id: id}, function(err, user) {
if (err) {
done(err, null); // something went wrong; return error
} else if (!user) {
done(null, false); // no user found; return `false`
} else {
done(null, user); // return user
}
});
});
for more info see here: Understanding passport serialize deserialize
I'm surprised that it's so hard to find, or maybe something wrong with me.
I need a passport.js strategy that requires no fields - to run authentication with it on simple user GET request to '/' and manipulate user session data to, for example, make a new 'guest' user.
Maybe i can do this via passport local-strategy? Or am i need to create a custom one?
I saw this Configure Passport to accept request without body? question, yet i just simply can't figure out how the suggested in answer wrapping will change anything.
Edit: here's what I'm trying to achieve
passport.use('guest', new LocalStrategy({
passReqToCallback: true
},
function (req, NOusername, NOpassword, done) {
////NO PASSWORD OR USERNAME checks here, just deciding what kind of temporary user needs to be created///
if (req.session.passport) {
///create new user with previous session data
done(null,user)
}
else {
///create clean user
done(null, user)
})
)
and then in routes.js
app.get('/', passport.authenticate('guest'), function (req, res) {
res.render('PeerRoom.ejs', req.user);
});
Edit: Another approach could be using req.logIn and skip dealing with the strategies altogether
app.get('/', yourCustomGuestAuthenticationMiddleware, function (req, res) {
res.render('PeerRoom.ejs', req.user);
});
function yourCustomGuestAuthenticationMiddleware(req, res, next){
// No need to do anything if a user already exists.
if(req.user) return next();
// Create a new user and login with that
var user = new User({name: 'guest'+Math.random().toString() });
user.save();
req.logIn(user, next);
}
To make a new guest user, simply do it instead of rejecting an authentication
Here's a modified example from the docs
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) return done(err);
if (!user) {
/* HERE, INSTEAD OF REJECTING THE AUTHENTICATION */
// return done(null, false, { message: 'Incorrect username.' });
/* SIMPLY CREATE A NEW USER */
var user = new User({ name: 'guest'+Math.random().toString() });
user.save();
/* AND AUTHENTICATE WITH THAT */
return done(null, user);
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
I'm new to node.js, and am trying to implement imap authentication for a simple test app using passport-imap.
I configured my ImapStrategy and login functions as stated in https://github.com/nettantra/passport-imap, but when I login to my app I get the message "Success fallback is required to generate the user!". Here's my code:
passport.use(new ImapStrategy({host: 'my-imap-server', port : 993, tls : true}, function(req,res){
console.log('AUTH HERE ' );
return done(null, user);
}));
app.post('/login', passport.authenticate('imap', {
failureRedirect : '/login', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}),function(req, res) {
console.log('OK AUTHENTICATED');
res.redirect('/app');
});
Adding a "successRedirect" param did not help... can anybody point me to the right direction?
I added a verification callback, but cannot understand what params does it need.
I tried doing something similar to localstrategy, but my "username" param is not what I expected (the user login), it's some other object. Also, I'm not sure this is the way to go: I actually don't want to keep a local userlist, I just want to accept any user that can authenticate to my imap server.
Also I don't understand whether I should use "nextTick" like localstrategy does: if I do it appears my verification is bypassed, and I'm always returned to the login page.
passport.use(new ImapStrategy({host: 'my-imap-server', port : 993, tls : true,
success_callback: function(username, password, done){
// process.nextTick(function () {
// Find the user by username. If there is no user with the given
// username, or the password is not correct, set the user to `false` to
// indicate failure and set a flash message. Otherwise, return the
// authenticated `user`.
findByUsername(username, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
if (user.password != password) { return done(null, false, { message: 'Invalid password' }); }
return done(null, user);
})
//});
} }
));
This is driving me crazy!
I'm using Express 4, passport and local-passport to authenticate login and signup.
I'm using this example:
https://github.com/tutsplus/passport-mongo
Problem:
When the signup form does not validate (say you forgot one of the fields) and we redirect to the failureRedirect (which is the same signup page), all the entered values are gone. Not a very good user experience that you have to fill out the entire form because you messed up a single field.
How do I pass the already entered data on the the form?
I got these two routes handing the GET and POST of the form:
app.get('/signup', function(req, res){
res.render('signup', {
message: req.flash('message'),
});
});
app.post('/signup', passport.authenticate('signup', {
successRedirect: '/signup-complete',
failureRedirect: '/signup', // need to pass the entered values (if any) back to the signup page
failureFlash : true
}));
I have a nagging suspicion that the values are already there - I just don't know how to grab them.
I'm using Swig for the views btw.
Did you add connect-flash middleware to your app?
var flash = require('connect-flash');
app.use(flash());
Update:
When you define your local strategy, you should return the flash messages in the third parameter of the done function. Example:
passport.use(new LocalStrategy(
function(username, password, done) {
findByUsername(username, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
if (user.password != password) { return done(null, false, { message: 'Invalid password' }); }
return done(null, user);
})
});
}
));