Can't make custom named local strategies work in PassportJS - node.js

So recently I needed to split my local strategy so it fits our new needs for different types of users logging to our website. Our code up to date looks like:
const localLogin = new LocalStrategy(localOptions, function(email, password, done) {
// Verify this email and password, call done with the user
// if it is correct email and password
// otherwise, call done with false
});
passport.use(localLogin);
And then in our users file we call authenticate like so:
const requireSignin = passport.authenticate('local', {session: false});
router.post('/signin', requireSignin, (request, response, next) => {
// User already had their email and password Auth'd
// Give him a token
});
We've just changed a few things to leave it like so:
passport.use('local.one', localLogin);
And then call it as follows:
const requireSignin = passport.authenticate('local.one', {session: false});
Tried everything we read in different answers but we can't get it to work. Seems so close yet so far... please help.
P.D.: We've also tried to get a response like:
passport.authenticate('local.one', function(err, user, info) {
console.log('HERE')
if (err) return next(err);
if (!user) return next(null, false);
next(null, user);
})
But we don't get any data from that.

Ok, after a lot of testing and digging a few conclusions just in case someone finds this same stone on their way:
LOGS! wow that was helpful...
After initialize passport on the express() object call this line to load custom config file:
require('./yourpathtoconfigfileHERE/yourconfigfile')(passport);
Also here is a link that pointed me in the right way:
Passport: Unknown authentication strategy "local"

Related

Passport-jwt authenticate not working well with node-jwt-simple

I'm using passport-jwt to authenticate some routes and I'm creating my jwts with node-jwt-simple/jwt-simple but facing some difficulties cause it looks like my passport-jwt authenticate middleware is not being called at all.
Here is my
passport-jwt-strategy
const jwtOpts = {
jwtFromRequest: ExtractJwt.fromHeader('Authorization'),
secretOrKey: secret,
};
passport.use(new jwtStrategy(jwtOpts, (payload, done) => {
console.log('payload ', payload.sub);
User.findById(payload.sub, (err, user) => {
if(err) { return done(err); }
if(!user) { console.log('didnt find!'); return done(null, false); }
done(null, user);
});
}));
which i'm then integrating it over here.
routes file
router.get('/success',
passport.authenticate('jwt', {session: false}),
async (ctx, next) => ctx.body = await "success!");
Here is also the way I make my jwt.
function tokenForUser(user) {
const timeStamp = new Date().getTime;
return jwt.encode({sub: user._id, iat: timeStamp}, secret);
}
//- Later in signup process
userToSave.save(async(err, user) => {
if(err) { return next(err); }
const token = await tokenForUser(user);
next(token);
});
//- If this helps, here is how my secret file looks like.
const secret = "JKAha23ja1ddHdjjf31";
export default secret;
Problem comes, when I hit that route i only get Unauthorized and in the console nothing gets logged out not even the 'payload' key I specified first.
I should also say that I have the token at ctx.request.get('Authorization') (Koa based) i think it's something like req.header('Authorization') with express in all routes.
Also The exact express based problem can be found on the github issues of node-jwt-simple here incase there is any problem with my code samples.
Thank you.
After I wrapped my head right i knew that this has been my horrible understanding of how the whole authentification process works.
When I decoded the token from ctx.get('Authorization') I got a different _id than the one stored in the db Because I had hardcoded Authorization header in postman and thought "If I ctx.set('Authorization', token); It will replace the one I hardcoded on postman".
Less did I think that this jwt will be included in a header of requests when I make http calls on front end.
I naively thought jwts are passed directly from the server to the browser (Something like how render works) and Not from the server to an ajax process which later embeds it in request made which is the correct way.
The whole code is awesome, except now I have to just pass the token ctx.body = token; after I created it when I signed up.
Thank You.

check if user logged in by passport Strategy (different user types)

I have two user collections in my db and I want to make different login types for every one, so I have made two strategy on passport for my site ('local-user' & 'local-manager').
My question is how to check logged in user type (by used strategy) in my app?
In this code, passport just checks user auth but I want to check by strategy. (eg: if user logged in by 'local-manager', then show the page)
function isLoggedIn(req, res, next){
if (req.isAuthenticated()) {
next();
return;
}
res.redirect('/login');
}
It's better you use role mapping for this.
Anyway for now you can use this concept :
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
{passReqToCallback: true},
function(req, username, password, done) {
req.usedStrategy = 'local-user';
//do auth stuff
});
}
));
And use like this :
function isLoggedIn(req, res, next){
if (req.isAuthenticated() && req.usedStrategy === 'local-user') {
next();
return;
}
res.redirect('/login');
}
Also you can use session if you enable it in passport.
It must be said (and has been in other answers/comments) that you should really look again at your modelling of the domain. User objects can be really simple (just login information) and the rest can be broken out into other models/schemas.
Anyway on to the answer to your original question:
You can switch on the user type. Passport doesn't reach too far into the rest of your application. The log in strategies are not known about outside of the actual log in section.
You can handle that as middleware and add some extra information to the request object by checking for a unique property in one of the models:
function(request, response, next){
request.isManager = !!(request.user && request.user['unique_prop']);
next();
}
Place this after the auth middleware. Then in your route you can switch based on request.isManager. Also encapsulating this in middleware will abstract it from the user model and allow you to refactor it in the background.
Another alternative would be to add the function as a static/method/virtual (depending on the implementation) to the schema if you're using mongoose.
Hope this helps 👍 If you have further questions feel free to add comments and I can amend the answer. 🤔
I know this question is old, but I just had the same problem, and I managed to find a way to at least know what kind of account the user is. As someone else said, I don't think there is a way to see what strategy a user used to log in, but if you have a field in your user database to show what kind of account they are (e.g.: admin, client, etc.), then you can pass this information along in the req.user variable when you serialize the user:
passport.serializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, {
id: user.user_id,
username: user.user_email,
type: user.user_type
});
});
});
passport.deserializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, user);
});
});
In the example above, I'm using an SQL database, and one of the columns is called user_type. Once the user is deserialized, passport passes on the fields you ask for in passport.serialize() to req.user. So, if you wanted to know what type of account is currently logged in, you could say something like:
console.log("This user is a(n) " + req.user.type);
Or, something more realistic, if you're using Express:
app.get("/adminsOnly", (req, res) {
if (req.isAuthenticated() { // returns true if a user successfully logged in
if (req.user.type === "admin") { //check if user is an admin
res.render("adminsOnly.ejs"); //render admin section of website if true
} else {
res.redirect("/adminsLogin"); //redirected somewhere else because user is not an admin
}
} else {
res.redirect("/login"); //req.isAuthenticated() returns false, so user is redirected to the login page
}
});
I'm pretty new to coding in general, so I'm sure there are better ways to tackle this question, but this is a way to work around the problem of not being able to pinpoint which strategy the user logged in with.

SailsJS Linkedin OAuth 2.0 Login Flow Issues

I'm trying to use this library to authenticate using Linkedin:
https://github.com/auth0/passport-linkedin-oauth2
No Linkedin Login Prompt
I have configured my Passport Linkedin Strategy like so:
var passport = require('passport');
var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
passport.use(new LinkedInStrategy({
clientID: 'LINKEDIN_API_KEY',
clientSecret: 'LINKEDIN_API_SECRET',
callbackURL: 'http://localhost:1337/auth/linkedin/callback',
scope: ['r_emailaddress', 'r_basicprofile'],
state: true
}, function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// To keep the example simple, the user's LinkedIn profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the LinkedIn account with a user record in your database,
// and return that user instead.
return done(null, profile);
});
}));
My AuthController.js looks like this:
var passport = require('passport');
module.exports = {
login: function(req, res) {
passport.authenticate('linkedin', function(err, user, info) {
// The request will be redirected to LinkedIn for authentication, so this
// function will not be called.
});
},
callback: function(req, res) {
// ------------------------------------------------------------------------
// after user authenticated, we get the user's email from
// Linkedin's JSON response and save it against the matching  
// email address in the User model
// ------------------------------------------------------------------------
console.log(res);
},
logout: function(req, res) {
req.logout();
res.send('logout successful');
}
};
From the linkedin oauth library, I expect the call to:
passport.authenticate('linkedin', function...);
In my AuthController's login action, to redirect the user to Linkedin's login prompt page but what I am actually seeing is my browser just keeps on loading, loading, loading and never stops.
Am I doing something wrong ?
Some questions I am not sure of:
Does Linkedin expect my server to be running on HTTPS before it lets this whole thing starts working ?
Is there some special configurations that I need to do in my Linkedin developers app setting ? (I've enabled all the correct Javascript SDK URLs)
Callback Error
OK, so continuing on, my next problem appears to be here:
return done(null, profile);
^
TypeError: object is not a function
My code is following the npm module instruction here: https://www.npmjs.com/package/passport-linkedin-oauth2
Maybe SailsJS has another way of writing it yet again....
Authentication Always Fails
After fixing the callback error as mentioned in my solution below, I decided to keep moving on and see how it goes despite the Linkedin documentation isn't quite matching 100% to what I expect from the NPM library.
My next problem is my authenticated.js policy appears to always fail.
My code is below:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.authenticated) { // <---- this is the error line
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
No Login Prompt Solution
sigh
I think I'm beginning to grasp some of the difference between SailsJS and pure ExpressJS codes.
The problem appears that I was missing this piece of code at the end of my passport.authenticate() method:
(req, res)
I picked it up after looking this tutorial again: http://iliketomatoes.com/implement-passport-js-authentication-with-sails-js-0-10-2/
So now, the final authenticate method should look like:
passport.authenticate('linkedin', function(err, user, info) {
// The request will be redirected to LinkedIn for authentication, so this
// function will not be called.
})(req, res); // <--- notice this extra (req, res) code here
Which matches the Passportjs documentation:
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);
});
In a way....if you know what I mean... :D
Now I got my Linkedin login prompt as expected.
Finally!
Callback Error Solution
OK.....I'm not sure if this is completes the login process...but....
I noticed I had an extra line:
passReqToCallback: true
Taken from this page here:
https://github.com/auth0/passport-linkedin-oauth2/issues/29
I removed that and I got a different error message.
I've also changed my callback code to look like:
passport.authenticate('linkedin', function(err, user, info) {
res.json(200, {
user: user
});
})(req, res);
and I got my user JSON which appears to be my Linkedin user profile info:
{
user: {
provider: "linkedin",
...
}
}
But that's...contradicting the Linkedin documentation...I don't see any access_token or expire_in properties which I was expecting to see in step 3 of the Linkedin OAuth 2.0 documentation (https://developer.linkedin.com/docs/oauth2)...
So...supposedly...I should take this user object and create/update against an existing user object ?
Authentication Always Fails Solution
OK, so few more days, I added extra code to generate a User entity if one isn't found in my database, otherwise just return the found user.
The was one last problem, in my policies folder, I have a authenticated.js and it looked like this:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.authenticated) { // <---- this is the error line
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
Being new to all this web development stuff, I thought:
req.authenticated; // should call match name of the file ?
was correct but I was following this tutorial:
http://iliketomatoes.com/implement-passport-js-authentication-with-sails-js-0-10-2/
and he named his file: isAuthenticated.js I figured it's just a name....but I was wrong :D
Turns out, the correct code was:
req.isAuthenticated()
So in full, the correct code becomes:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.isAuthenticated()) { // alright, that's more like it!
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
Perhaps isAuthenticated is a Passportjs function and not just a name like I initially thought.
My further research shows this page which suggests so to me:
Problems getting Passport.js to authenticate user
Maybe req.authenticated can only be used for HTML email-password login form as suggested in above Stackoverflow post and req.isAuthenticated() is for OAuth stuff.
Anyhow, I still don't know if this is the right path but so far, I got authentication in my application now and I can access protected resources. Not sure how long I'll be logged in for, maybe I still need to build the refresh token thingo every 15 minute like the Linkedin documentation stated ?
Hope this helps other fellow Sailsjs users who are facing the same problem :)
Does Linkedin expect my server to be running on HTTPS before it lets
this whole thing starts working ?
No. The API works just as well on a local http setup.
Is there some special configurations that I need to do in my Linkedin
developers app setting ? (I've enabled all the correct Javascript SDK
URLs)
No, your setup is fine.
The browser keeps loading because after the authentication LinkedIn redirects to your callback action which isn't handling the response stream.
You need to handle the response in the callback action. Something like this will do:
callback: function(req, res) {
passport.authenticate('linkedin', function(err, user){
// handle error
// do something with the user (register/login)
return res.redirect('/home');
});
}
I'd highly recommend using sails-generate-auth for maintaining third-party logins. Very easy to setup and configure. All you need to do is serve the access tokens and secrets for the different strategies (either through config/passport.js or, preferably, through config/local.js). Will spare you a lot of redundant code.

Passport.js Session Confusion

I am learning node and express. I am trying to build a very basic app that will simply let a user log in using json. Then will maintain session until they log out. With asp.net this is a doddle you just set it up in the config and call...
Auth.Login(username,pasword)
When they log out you just do:
Auth.logout()
And if you need to check if they are logged in you simply do:
Auth.IsLoggedIn()
Or code to that effect. Well seems like Passport for node is just not that simple. I spent all night getting this working...
app.post('/authentication/login', function handleLocalAuthentication(req, res, next) {
passport.authenticate('local', function(err, user, info) {
// Manually establish the session...
req.login({username:'me#me.com',password:'password'}, function(err) {
if (err) return next(err);
return res.json({
message: 'user authenticated'
});
});
})(req, res, next);
});
app.get('/authentication/isauthenticated',function(req,res){
console.log(req.isAuthenticated());
})
passport.use(new LocalStrategy(
function(username, password, done) {
return done(null, {username:'ss',password:'sffds'});
}
));
So now I have no cookies, no session persisted when I login and then hit the /authentication/isAuthenticated url. I can't even get a breakpoint to stop in the strategy...
passport.use(new LocalStrategy(
function(username, password, done) {
console.log('ggg');
return done(null, {username:'ss',password:'sffds'});
}
));
Am I looking at the wrong solution for this? Should I be rolling my own auth or something with a basic middleware function?
Check out this tutorial. It's really great and it helped me a lot.
And here's my repo which has implemented passport authentication with users stored in mongodb through mongoose, and hashed passwords. Clone it or just check it out, it should help.
https://github.com/thyforhtian/auth_base.

How can I report an invalid login properly with Express and PassportJS?

I've successfully implemented passport-local into my Express/Mongoose web-app but I'm having trouble figuring out how to render a failed login message properly.
Here's my login route:
app.get('/login', function(req, res) {
res.render('user/login', {
});
});
With a route like that how am I supposed to report an invalid login? If the login is successful it will write the id/username to the req.user object but that doesn't help me in the "GET /login" route because if it's successful you will get redirected to the page you want to go.
That means req.user will always be undefined when you GET the login page.
I want to be able to write out a message saying something like 'yo, invalid login!' when the following things happen:
The user does not exist.
The password supplied does not match but the user existed.
I might want to output a different message depending on what occurred.
When I implemented the LocalStrategy I used this code:
passport.use(new LocalStrategy({
usernameField: 'email'
},
function(email, password, fn) {
User.findOne({'login.email': email}, function(err, user) {
// Error was thrown.
if (err) {
return fn(err);
}
// User does not exist.
if (!user) {
return fn(null, false);
}
// Passwords do not match.
if (user.login.password != utility.encryptString(user.login.salt + password)) {
return fn(null, false);
}
// Everything is good.
return fn(null, user);
});
}
));
As you can see there are some problems but this is how the author of PassportJS set up his application. How are we supposed to access what the Strategy returns?
Like if it throws an error, what am I supposed to even call to get access to err?
Thanks.
In the latest version of Passport, I've added support for flash messages, which make it easy to do what you are asking.
You can now supply a third argument to done, which can include a failure message. For example:
if (user.login.password != utility.encryptString(user.login.salt + password)) {
return fn(null, false, { message: 'yo, invalid login!' });
}
Then, set failureFlash to true as an option to authenticate().
passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login',
failureFlash: true });
In this case, if authentication fails, the message will be set in the flash, ready for you to render it when you display the login page again.
Custom callbacks are also perfectly fine. The built in options just make it simpler to accomplish common tasks.
Also, I'm curious: you mention that there are problems with the sample. What do you think should be improved? I want to make the examples as good as possible. Thanks!
(For more details, see this comment on issue #12).
You can use the custom callback or middleware functionality to have more control. See the Authentication section of the guide for examples.
For example, a custom callback might look like:
app.get('/login', function(req,res,next) {
passport.authenticate('local', function(err,user) {
if(!user) res.send('Sorry, you\'re not logged in correctly.');
if(user) res.send('Skeet skeet!');
})(req,res,next);
});
Alternatively, you could always redirect both responses:
app.get('/login',
passport.authenticate('local', { successRedirect: '/winner',
failureRedirect:'/loser' }));
Or redirect the failure with simple middleware:
app.get('/login', ensureAuthenticated,
function(req,res) {
// successful auth
// do something for-the-win
}
);
// reusable middleware
function ensureAuthenticated(req,res,next) {
if(req.isAuthenticated()) {return next();}
res.redirect('/login/again'); // if failed...
}

Resources