In my mean js app i have an account model and corresponding routes and controllers. To remove a specific account i need to have authorization and I need to be logged in.
All users can however list all accounts, I only wont to list the accounts created by the specific user. So i need to add autorization to the list part of the code.
I update the routes for app.route('/accounts') with users.requiresLoginandaccounts.hasAuthorization as shown below:
module.exports = function(app) {
var users = require('../../app/controllers/users.server.controller');
var accounts = require('../../app/controllers/accounts.server.controller');
// Accounts Routes
app.route('/accounts')
.get(users.requiresLogin,accounts.hasAuthorization,accounts.list)
.post(users.requiresLogin, accounts.create);
app.route('/accounts/:accountId')
.get(users.requiresLogin, accounts.hasAuthorization,accounts.read)
.put(users.requiresLogin, accounts.hasAuthorization, accounts.update)
.delete(users.requiresLogin, accounts.hasAuthorization, accounts.delete);
// Finish by binding the Account middleware
app.param('accountId', accounts.accountByID);
};
Now I get an errror since req is not provided with user.
GET /modules/accounts/views/list-accounts.client.view.html 304 8.266
ms - - TypeError: Cannot read property 'user' of undefined
at exports.hasAuthorization (/Users/david/Repositories/budget/app/controllers/accounts.server.controller.js:103:17)
So I imagine i need to update the accounts.server.controller somehow. The delete account does provide an account in the req, so that only the creator can delete as I mentioned earlier. How do I update the code so that the "List of accounts" part work and list only the accounts belonging to that specific user?
/**
* Delete an Account
*/
exports.delete = function(req, res) {
var account = req.account ;
account.remove(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(account);
}
});
};
/**
* List of Accounts
*/
exports.list = function(req, res) {
Account.find().sort('-created').populate('user', 'displayName').exec(function(err, accounts) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(accounts);
}
});
};
/**
* Account middleware
*/
exports.accountByID = function(req, res, next, id) {
Account.findById(id).populate('user', 'displayName').exec(function(err, account) {
if (err) return next(err);
if (! account) return next(new Error('Failed to load Account ' + id));
req.account = account ;
next();
});
};
/**
* Account authorization middleware
*/
exports.hasAuthorization = function(req, res, next) {
if (req.account.user.id !== req.user.id) {
return res.status(403).send('User is not authorized');
}
next();
};
The account client service only contains the basic stuff:
//Accounts service used to communicate Accounts REST endpoints
angular.module('accounts').factory('Accounts', ['$resource',
function($resource) {
return $resource('accounts/:accountId', { accountId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
And the user object is not mentioned in the controller.
The accounts.hasAuthorization assume it get executed after the accounts.accountById ,on your current configuration req.account will be undefined.
I'm assumming that somewhere in your account model you have:
user: {
type: Schema.ObjectId,
ref: 'User'
}
If you want the user only have access only to the accounts he/she owns :
Change accounts.list route only to requires Login and this gives us access to the req.user :
app.route('/accounts')
.get(users.requiresLogin,accounts.list)
Change the exports.list in the accounts controller:
exports.list = function(req, res) {
Account.find({user: req.user._id}).sort('-created')
.... //
};
Related
I followed a tutorial to add login and registration to my Node.js app using JWT token and I'm having a hard time logging in and redirecting to my 'logged in' admin page. User registration works great, but the login portion I can't figure out.
This is the tutorial I was following:
https://medium.freecodecamp.org/learn-how-to-handle-authentication-with-node-using-passport-js-4a56ed18e81e
My code for login looks like this:
router.post('/login', auth.optional, (req, res, next) => {
console.log(req.body);
var user = {
email: req.body.email,
password: req.body.password
}
if (!user.email) {
return res.status(422).json({
errors: {
email: 'is required',
},
});
}
if (!user.password) {
return res.status(422).json({
errors: {
password: 'is required',
},
});
}
return passport.authenticate('local', { session: false }, (err, passportUser, info) => {
if (err) {
return next(err);
}
if (passportUser) {
const user = passportUser;
user.token = passportUser.generateJWT();
console.log("TOKEN: " + user.token);
res.setHeader('Authorization', 'Token ' + user.token);
return res.json({ user: user.toAuthJSON() });
}
return res.status(400).json({
errors: {
message: info,
},
});
})(req, res, next);
});
My '/admin' "logged in" route looks like this:
router.get("/admin", auth.required, function(req, res) {
res.render('admin', {
user : req.user // get the user out of session and pass to template
});
});
I'm not sure how I can redirect to my '/admin' route while also passing the token because currently I am seeing the following error after logging in. Makes sense since I am not passing the token to the '/admin' route...but how do I do that? :)
UnauthorizedError: No authorization token was found at middleware
Thanks in advance for the help!
EDIT:
Still can't figure this out and don't really understand how this flow is supposed to work...where do the headers need to be set to the token and how do I redirect to my admin page once the login is successful.
Here is my middleware code if this helps:
const getTokenFromHeaders = (req) => {
console.log("REQ: " + JSON.stringify(req.headers));
const { headers: { authorization } } = req;
if(authorization && authorization.split(' ')[0] === 'Token') {
return authorization.split(' ')[1];
}
return null;
};
const auth = {
required: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
}),
optional: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
credentialsRequired: false,
}),
};
Your code does not have a problem. You seem to be confused with the login flow from server to client (Frontend/Web).
Let's first have a look the RESTFUL way of doing it. The article also refers to the same flow.
The RESTFUL API flow looks like this:
User requests for login:
POST: /api/v1/auth/login with username and password in request body.
If successful, user is returned with basic inforamtion and token.
If not, user is returned a 401 (Unauthorized) status code.
The login flow ends here.
The token provided earlier to the user is used to make subsequent calls to the backend, which a user can use to perform different operations on the sustem. In essence, it is the client which requests server for subsequent actions with the token provided in the login request.
So for your case, user after receiving the token should make a request for retrieving admin information from the backend.
But, I am assuming you are rendering views from your server-side and you want to render the admin view once the user is successfully logged in, and that's pretty straight forward.
Instead of your res.json() after successful login. You need to use res.render().
res.render('admin', {
user: user.toAuthJSON() // assuming your user contains the token already
})
Edit:
Since res.render() does not change the url in the browser. For that, you need to use res.redirect(). But the problem is, you can not send context in res.redirect().
To achieve that, you will need to pass in the user token as query paramter. See here.
TL;DR
// assuming you are using Node v7+
const querystring = require('querystring');
const query = querystring.stringify({
token: user.token,
});
const adminRoute = '/admin?' + query;
res.redirect(adminRoute)
And in your admin route, you need to slightly modify the code.
Verify the token belongs to a real user and get user information out of the token.
Render the admin template with user information retrieved from step 1.
router.get("/admin", function(req, res) {
// verify the token
const token = req.query.token;
const user = null;
jwt.verify(token, 'secret', function (err, decoded) {
if (err) {
res.status(401).send('Unauthorized user')
}
// decoded contains user
user = decoded.user
});
res.render('admin', {
user : user
});
});
I'm somewhat new to this as well, but I've got it working as follows.
In your server.js file:
const passport = require("passport");
const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require("passport-jwt").ExtractJwt;
app.use(passport.initialize());
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = Keys.secretOrKey;
passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
// somefunction looks up the id in jwt payload and
// supplies passport the authenticated user via the "Done" function
somefunction.user(jwt_payload.id)
.then(user => {
if (user) {
return done(null, user);
}
return done(null, false);
});
})
);
In your API definitions
const jwt = require("jsonwebtoken");
router.post("/login", (req, res) => {
const { userInfo } = req.body;
// userInfo has username and password in it
// anotherFuction validates the user id and password combo
anotherFunction(userInfo.id, userInfo.password)
.then(isAuthenticated => {
if (isAuthenticated) {
const payload = {
id: user.sAMAccountName,
firstname: user.givenName,
lastname: user.sn
};
// Sign Token with the payload
jwt.sign(
payload,
Keys.secretOrKey,
{ expiresIn: 3600 },
(err, token) => {
res.json({
success: true,
token: "Bearer " + token
});
}
);
} else {
// don't mind the statuses ^_^'
return res.status(401).json({ error: "Login failed." });
}
})
.catch(err => {
return res.status(400).json(err);
});
});
After calling the API you want to set the auth token. The following lets you delete the token if nothing is passed in, effectively "Logging out".
const setAuthToken = token => {
if (token) {
// Apply to every request
axios.defaults.headers.common["Authorization"] = token;
} else {
// Delete Auth Header
delete axios.defaults.headers.common["Authorization"];
}
};
If you're trying to use it in the front end, you need to use jwt_decode to pull the values from the token and set it however you deem necessary. If using redux to store login data it should look something like this. As I feel that the discussion of using localstorage for jwtToken is outside of the scope of this, just know would need to check for the token.
if (localStorage.jwtToken) {
setAuthToken(localStorage.jwtToken);
const decoded = jwt_decode(localStorage.jwtToken);
store.dispatch({
type: USER_LOGIN,
payload: decoded
});
}
Hope this helped.
From one beginner in JWT to another. Good luck.
I am trying to set up a simple authentification system with Parse Server:
app.js
...
app.get('/login', (req, res) => {
res.render('login.ejs');
});
app.post('/login', (req, res) => {
console.log('POST /login\t' + util.inspect(req.body));
driver.login(req, (err, user) => {
//Here, user is defined
if(err) {
res.redirect('/login');
} else {
res.redirect('/user');
}
});
});
...
driver.js:
...
function login(req, callback) {
var username = req.body.username,
password = req.body.password;
Parse.User.logIn(username, password, {
success: (user) => {
callback();
},
error: (user, error) => {
callback(JSON.stringify(error));
}
});
}
function isLoggedIn(req, callback) {
console.log('isLoggedIn?');
console.log(util.inspect(req.user)); //undefined
if(req.user) {
callback();
} else {
callback('Not logged in');
}
}
...
When I access /login, I can login just fine, and get redirected to /user without any error, but on /user, which use isLoggedIn as a middleware, req.user is undefined.
I have seen others with the same problem when searching, but the post where either old (<2015), using another part of the JSSDK (react/browser), or just didn t get any answer.
I know I could use session, and recreate the user each time based on that, but it feels really hackish, is it really the supported way?
You have two routes to go, either have a REST-full server, which means users are not persistent between route calls, STATE-full and use sessions.
Luckily there is a really good nodejs authentication middleware already build that will handle all this session managment. This is called passportJS. Documentation can be found here: http://passportjs.org/docs
You can not only have authentication through local logins, but have support for authentication with google, facebook, github and many more. This is done through what are called Strategies. You use google-strategy for having google oauth, facebook-stradegy for facebook oauth, etc.
What you would be looking for is a local-strategy, which is called this because you want to authenticate with local user credentials. This strategy can be found here: https://www.npmjs.com/package/passport-local
you will need both passport and passport local and to install simply run
npm install passport passport-local
From there, just go through the documentation I have linked above for how to set everything up.
To develop my answer to blablabla comment, who asked what I ended up using.
In my case, I was develloping a REST API, and the session data wasn't expected to change once connected.
So what I did is delegate the session to the client, and go full stateless, by using JWT.
When the client is authentified, I encrypt his session data in a Json Web Token, and send him. When he try to access a protected page, he send me the JWT, which I can decrypt, and recreate req.user based on the information within.
My middleware look like this:
function isLoggedIn(req, res, next) {
//If there is a token
if(req.headers != null && req.headers.token != null) {
//Verify it and extract the user's data
verify(req.headers.token, (err, user) => {
if(err != null) {
res.status(401);
res.json({error: err});
} else {
//Recreate req.user
req.user = user;
next();
}
});
} else {
res.status(401);
res.json({error: 'No token'});
}
}
function verify(token, callback) {
//Verify the token
jwt.verify(token, jwtSecret, (error, user) => {
if(error != null) {
callback(error);
} else {
Separately check if logged into Parse
parse.isLoggedIn(user, (error, loggedIn) => {
if(error != null) {
callback(error);
} else if(!loggedIn) {
callback('Not logged in the Parse');
} else {
callback(null, user);
}
});
}
});
}
I am currently trying to set up an admin role in order to access a simple admin page using the following documentation provided via : connect-roles
I ave been banging my head against it for a while and am still lost on how to set a role E.G As of right now am pulling a admin value out of the DB and storing it in a global var for the time being but I have no idea how to use that with connect-roles say to only allow access to my admin page for a specific user.
Can anyone clarify or show an example on how to do this/some guidance as I documentation didn't help me to ensure access to a web page only if the user is an admin?
Ave posted some of the code kinda showing what it looks like at the moment.
Code
var admin = 'Admin';
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '',
database : 'test'
});
var passport = require('passport');
var ConnectRoles = require('connect-roles');
var roles = new ConnectRoles();
var passportLocal = require('passport-local');
app.use(passport.initialize());
app.use(passport.session());
app.use(roles.middleware());
passport.use(new passportLocal.Strategy(function (username, password, done) {
connection.query({
sql : 'SELECT * from `userman_users` WHERE `username`= ?AND`password` = sha1(?)',
timeout : 40000, // 40s
values : [username, password]
}, function (error, results, rows) {
if (results.length > 0) {
response = "Success";
} else {
console.log('Error while performing Query.');
response = "Failed";
}
if (response === "Success") {
done(null, {
id : username
});
} else if (response === "Failed") {
done(null, null);
}
});
})
);
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (id, done) {
done(null, {
id : id
});
});
roles.use(function (req, action) {
if (!req.isAuthenticated()) return action === 'access home page';
})
roles.use(function (req) {
if (req.user.role === 'admin') {
return true;
}
});
app.get('/', redirectToIndexIfLoggedIn, function (req, res) {
res.render('login');
});
app.get('/index', checkLoggedIn, function (req, res) {
res.render('index', {
isAuthenticated : req.isAuthenticated(),
user : req.user
});
});
app.get('/admin', user.can('access admin page'), function (req, res) {
res.render('admin');
});
function checkLoggedIn(req, res, next) {
if (req.isAuthenticated())
return next();
res.redirect('/');
}
this is an example:
var express = require('express');
...
var passport = require('passport');
var LocalStrategy = require('passport-local');
var ConnectRoles = require('connect-roles');
...
var app = express();
//===============PASSPORT=================
// Passport session setup.
passport.serializeUser(function(user, done) {
console.log("serializing " + user.username);
done(null, user);
});
passport.deserializeUser(function(obj, done) {
console.log("deserializing " + obj);
// simulate an admin user
obj.role = obj.username == 'admin' ? 'admin' : 'user';
done(null, obj);
});
...
//===============CONNECTION RULES=================
var user = new ConnectRoles({
failureHandler: function (req, res, action) {
// optional function to customise code that runs when
// user fails authorisation
var accept = req.headers.accept || '';
res.status(403);
if (~accept.indexOf('html')) {
res.render('access-denied', {action: action});
} else {
res.send('Access Denied - You don\'t have permission to: ' + action);
}
}
});
...
app.use(passport.initialize());
app.use(passport.session());
app.use(user.middleware());
//anonymous users can only access the home page
//returning false stops any more rules from being
//considered
user.use(function (req, action) {
if (!req.isAuthenticated()) return action === 'access home page';
});
//users logged can access to public pages
user.use(function(req, action){
if(req.isAuthenticated() && action != 'access private page' && action != 'access admin page')
return true;
});
//moderator users can access private page, but
//they might not be the only ones so we don't return
//false if the user isn't a moderator
user.use('access private page', function (req) {
console.log('access private page');
if (req.user.role === 'moderator') {
return true;
}
});
//admin users can access all pages
user.use(function (req) {
if (req.user.role === 'admin') {
return true;
}
});
...
/* GET home page. */
app.get('/', user.can('access home page'), function(req, res, next) {
res.render('index', { title: 'Express' });
});
//displays our signup page
app.get('/signin', function(req, res){
res.render('signin');
});
//sends the request through our local signup strategy, and if successful takes user to homepage, otherwise returns then to signin page
app.post('/local-reg', passport.authenticate('local-signup', {
successRedirect: '/',
failureRedirect: '/signin'
})
);
//sends the request through our local login/signin strategy, and if successful takes user to homepage, otherwise returns then to signin page
app.post('/login', passport.authenticate('local-signin', {
successRedirect: '/',
failureRedirect: '/signin'
})
);
// Simple route middleware to ensure user is authenticated.
app.use(function(req, res, next) {
if (req.isAuthenticated()) { return next(); }
req.session.error = 'Please sign in!';
res.redirect('/signin');
});
//logs user out of site, deleting them from the session, and returns to homepage
app.get('/logout', function(req, res){
var name = req.user.username;
console.log("LOGGIN OUT " + req.user.username)
req.logout();
res.redirect('/');
req.session.notice = "You have successfully been logged out " + name + "!";
});
app.get('/private', user.can('access private page'), function (req, res) {
res.render('private');
});
app.get('/admin', user.can('access admin page'), function (req, res) {
res.render('admin');
});
app.use('/users', users);
....
module.exports = app;
With connect-rules you define the rules do you want to use (user.use in this case). If you pass an action as first parameter the strategy is only used if the action passed in the function is equal to it. Then you trigger the rules in the routes with user.can passing the action. In this example I define an extra filter strategy to grant access to users that are logged and request routes that are not marked with admin or moderator privileges e.g
/* GET home page. */
app.get('/', user.can('access home page'), function(req, res, next) {
res.render('index', { title: 'Express' });
});
After the user is logged, we need to have another strategy in case the user isn't admin or moderator.
U can use framework like sailsJS and npm module sails-generate-auth
And after setup, use your own middleware to block routes
//allow admin only localhost:PORT/admin at policies.js
'admin': ['passport', 'sessionAuth', 'isAdmin'],
'*': ['passport', 'sessionAuth'],
//isAdmin policy
module.exports = function(req, res, next) {
// User is allowed, proceed to the next policy,
// or if this is the last policy, the controller
if (req.user.role == 'admin') {
return next();
}
// User is not allowed
return res.forbidden('You are not permitted to perform this action.');
};
Using the following logic I was able to have admin functionality based on value within the DB:
app.get('/admin', function (req, res) {
connection.query({
sql : 'SELECT role from `auth_users` WHERE `username`= ?',
timeout : 40000, // 40s
values : [req.user['id']]
}, function (error, results, rows) {
if (results[0]['role'] === "admin") {
admin = (results[0]['role']);
res.render('admin', {
isAuthenticated : req.isAuthenticated(),
user : req.user
});
} else {
admin = "";
res.redirect('/index');
}
})
});
Im trying to update the req.conversation before its handled by another function called read. I know the middleware is being called before read but when I log the req.conversation object in read it doesnt reflect the updates made in the middleware.
/**
* Conversation middleware
*/
exports.conversationByID = function(req, res, next, id) {
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).send({
message: 'Conversation is invalid'
});
}
Conversation.findById(id).populate('user', 'displayName').populate('readers', 'displayName').exec(function(err, conversation) {
if (err) return next(err);
if (!conversation) {
return res.status(404).send({
message: 'Conversation not available'
});
}
req.conversation = conversation;
next();
});
};
Where is the id parameter in your middleware callback coming from? If it's a url param (e.g. /conversations/:id) it should be req.params.id.
I've just started with meanjs. When I've taken a look at it server side module user profile controller, I find that mongoose model User is available in req object.
From where it has got added in req object?
Refer the code below, I wants to understand var user = req.user;, How user is added in req object?
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash'),
errorHandler = require('../errors.server.controller.js'),
mongoose = require('mongoose'),
passport = require('passport'),
User = mongoose.model('User');
/**
* Update user details
*/
exports.update = function(req, res) {
// Init Variables
var user = req.user;
var message = null;
// For security measurement we remove the roles from the req.body object
delete req.body.roles;
if (user) {
// Merge existing user
user = _.extend(user, req.body);
user.updated = Date.now();
user.displayName = user.firstName + ' ' + user.lastName;
user.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
req.login(user, function(err) {
if (err) {
res.status(400).send(err);
} else {
res.json(user);
}
});
}
});
} else {
res.status(400).send({
message: 'User is not signed in'
});
}
};
/**
* Send User
*/
exports.me = function(req, res) {
res.json(req.user || null);
};
In meanjs app.param([name], callback) is used, whenever a route with some id like articeId in parameter is accessed, app.param([name], callback) middleware is triggered. In meanjs it sets req.article like this.
app.param('articleId', articles.articleByID);
and in articleByID
exports.articleByID = function(req, res, next, id) {
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).send({
message: 'Article is invalid'
});
}
Article.findById(id).populate('user', 'displayName').exec(function(err, article) {
if (err) return next(err);
if (!article) {
return res.status(404).send({
message: errorHandler.getErrorMessage(err)
});
}
req.article = article;
next();
});
};
more on app.param see http://expressjs.com/api.html#app.param
Passport’s authentication middleware sets req.user upon successful login.
See http://passportjs.org/guide/authenticate/.