I am currently working on a text based game with a small team of developers. The game requires login and we are using the MEAN (MongoDB, Express, Angular, Node) Stack for the application codebase, however i am stuck on authentication, as a rails developer i am used to being able to drop in a gem and use the helpers available.
has anybody has any experience with MEAN and Authentication?
the MEAN stack by linnovate uses Passport.js for its authentication. Passport uses different strategies for authentication. One of these strategies is a username and password pair, which they call LocalStrategy.
Here is one of the samples from the Passportjs-Local Github Examples Page
Step 1: Require Passport
First you require the module after doing npm install passport
var passport = require('passport');
Step 2: Configure 'Verify' Function
Use the LocalStrategy within Passport. Strategies in passport require a verify function, which accept credentials (in this case, a username and password), and invoke a callback with a user object. In the real world, this would query a database; however, in this example we are using a baked-in set of users.
passport.use(new LocalStrategy(
function(username, password, done) {
// 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);
})
});
}
));
Step 3: Initialize Passport on app
You need to tell Express that you will be using passport and that it will be managing sessions for you. This is done by using the app.use() during app configuration.
app.use(passport.initialize());
app.use(passport.session());
Step 4: Configure Middleware on the login URI
Next we need to create a method that will accept when a user tries to login to the app using by POST-ing to a specific URI. It will look like this.
// POST /login
// Use passport.authenticate() as route middleware to authenticate the
// request. If authentication fails, the user will be redirected back to the
// login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page.
//
// curl -v -d "username=bob&password=secret" http://127.0.0.1:3000/login
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login', failureFlash: true }),
function(req, res) {
res.redirect('/');
});
Step 5: Set up Sessions
You may have to create your own serialization for User objects that are being stored in the sessions. That is done with the following
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing.
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
findById(id, function (err, user) {
done(err, user);
});
});
You can have a look at http://meanjs.org/
They have a very solid integration of passport.js strategies.
Especally useful is the implementation of Salt and Crypto-Technies to make the integration safe. Search for Salz within the repo.
See
https://github.com/meanjs/mean/blob/master/modules/users/server/config/strategies/local.js
For serialization and deserialization.
Or if you'd prefer a custom implementation, I recently posted a complete MEAN Stack User Registration and Login Example
Here's the snippet from the user service that handles authentication:
function authenticate(username, password) {
var deferred = Q.defer();
usersDb.findOne({ username: username }, function (err, user) {
if (err) deferred.reject(err);
if (user && bcrypt.compareSync(password, user.hash)) {
// authentication successful
deferred.resolve(jwt.sign({ sub: user._id }, config.secret));
} else {
// authentication failed
deferred.resolve();
}
});
return deferred.promise;
}
Or use mean.io which has user management out of the box.
Related
I am a beginner to nodejs and I am creating my web app. I use passportJs for authentication. As it is mentioned in the documentation that when the user is successfully authenticated, req.user will be created and can be accessed in any route.
My admin.handlebars
router.get('/' , (req , res)=>{
const current_user = req.user
if (!req.user) {
res.redirect('/login')
} else{
res.render('admin/index',{user_data:current_user })
}
})
Authentication using passportJS
passport.use(new LocalStrategy({usernameField:'email'}, (email,password,done)=>{
userModel.findOne({email:email}).then((user)=>{
if (!user) return done(null,false,{message: "No User found"})
bcrypt.compare(password,user.password,(err,matched)=>{
if(err)return err
if(matched){
return done(null,user)
}else {return done(null,false,{message:"Incorrect Password"})}
})
})
}))
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
userModel.findById(id, function(err, user) {
done(err, user);
});
});
router.post('/login',
(req,res,next)=>{
passport.authenticate('local', { successRedirect: '/admin',
failureRedirect: '/login',
failureFlash: 'Invalid username or password.'}
)(req,res,next)}
)
Here is my question:
As you see user will be redirected to admin page only if the .user exists in the req. So can a hacker add an empty .user to the request and access my admin page?
Its kind of weird question tho. Is there any better way to do this? Thanks in advance :)
End-user(in your case hacker) can add any type of data to any request. So yes, end-user can modify requests to send req.user within it. However, they won't be able to access the data within it and their request will not be accepted on your "admin" endpoint if you use req.isAuthenticated().
This is because passport JS serialises the user and stores the information in session after encryption. So UNLESS the end-user (Hacker) has access to another user's machine and copies all the session details (Browser's don't allow other sites to access another sites session) from their browser and use it, they won't be able to use admin.
TLDR;
No they wont be able to access "admin" endpoint by simply adding req.user in their request.
I have a NodeJS API server using express and passport for authentication. I am using the passport azure ad bearer strategy for authentication with Microsoft Azure AD. In the examples provided in the documentation(https://github.com/Azure-Samples/active-directory-node-webapi/blob/master/node-server/app.js#L50), the owner (currentUser) is a javascript variable defined globally. How do I attach the user to the request so that I can do something like
server.post('/user', passport.authenticate('oauth-bearer', {
session: false
}), function(req, res, next){
console.log(`I am the current user ${req.user}`);
});
From the example mentioned in the OIDC strategy, I have seen a deserializeUser and serializeUser function that would allow the user ID to be stored in the session, However, in the bearer strategy, we do not have any sessions maintained as authentication will be performed for every incoming request.
This may be a gap in my understanding. I have gone through the documentation for the individual libraries. Please help me if there is a way this can be done
Following is the sample code via which you can embedd the yser object to request object . Thus when you will do ${req.user} in post callback you will get the user object
const BearerStrategy = require('passport-azure-ad').BearerStrategy;
var bearerStrategy = new BearerStrategy(options,
function(token, done) {
findById(token.oid, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
// "Auto-registration"
return done(null, token);
}
const owner = token.oid;
return done(null, user, token);
});
}
);
I am not getting the exact details about which arguments need to be passed in the Passport Strategy. It would be very helpful if someone points me to the documentation.
For example:
passport.use(new LocalStrategy(
function(username, password, done){}));
Here the callback function of the LocalStrategy requires username, password and done.
I just want to know where it is documented and also for BearerStrategy.
If you need to create your own strategy you will want to extend passport-strategy. It can be found here with documentation: https://github.com/jaredhanson/passport-strategy
The parameters will be up to you to define depending on the needs of your custom strategy.
I just want to know where it is document
Passport local <---this shows which arguments need to be passed to configure passport
The local authentication strategy authenticates users using a username
and password. The strategy requires a verify callback, which accepts
these credentials and calls done providing a user.
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); }
if (!user.verifyPassword(password)) { return done(null, false); }
return done(null, user);
});
}
));
You compare the username from the name attribute of your form to the one in your database. I think you were asking for documentation so I provided the link above
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.
I have implemented Passport with passport-local and MongoDB and it is working nicely.
However this is a pure client-side single-loading app and so node is not responsible for the rendering of html. So currently I show a loading a spinner on app load and make a separate call to an api to determine if the user is logged in to conditionally render some stuff:
router.get('/me', function (req, res) {
res.send(req.isAuthenticated() ? {} || 401);
});
Since passport already authenticates my routes and calls deserializeUser this seems pointless - I need a way to pass an extra piece of info (in the cookie?) stating that the user is authed, I am guessing in deserializeUser?
server.use(session({secret: settings.sessionSecret}));
server.use(passport.initialize());
server.use(passport.session());
....
passport.use(new LocalStrategy(
localOpts,
function(email, password, done) {
User.findOne({
email: email,
activated: true
}, function (err, user) {
....
});
}
));
passport.serializeUser(function (user, done) {
done(null, user._id);
});
passport.deserializeUser(function (id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
Note that the two cookies that get created when sign in is successful:
express:sess
express:sess.sig
When it detects the presence of these cookies it seems to just call deserializeUser hence why I think I could possibly communicate to the client the user is authed there, or otherwise on sign in inside passport.use middleware?
It turns out that I can simply add a middleware after the passport.session. I was concerned that req.isAuthenticated would fire off another query to the db but it doesn't:
server.use(function(req, res, next) {
res.cookie('isAuthenticated', req.isAuthenticated());
next();
});