i've got a problem
I'm trying to make a simple login page,
but i've problem with passing the token through http header
app.post('/login',(req,res) => {
var body = req.body.user;
User.findByCredentials(body.email,body.password).then((user) => {
return user.generateAuthToken().then((token) => {
res.header('x-auth', token).send(user);
});
}).catch((e) => {
res.status(400).send();
});
});
here is the route for login page, I saved the token in 'x-auth' in header, and it's work
but...
var authenticate = (req, res, next) => {
var token = req.header('x-auth');
User.findByToken(token).then((user) => {
if (!user) {
return Promise.reject();
}
req.user = user;
req.token = token;
next();
}).catch((e) => {
res.status(401).send();
});
};
module.exports = {authenticate};
this function is middle-ware for privet routes, when I asking for 'x-auth' i've got 'undifined'
here is the piece that connect between both codes
app.get('/',authenticate,(req,res) => {
res.sendFile(publicPath + '/index.html');
});
someone can help me with that?
Related
I'm having a hard time connecting the last dots building a role based access control api in Express.
Following this tutorial and implementing onto my existing program, but I think I am missing the last step and after countless tutorials analysis paralysis has set in. I have since scaled back all my necessary code to what I think is the bare minimum.
Currently I am able to create a new user and save them to the mongoose database. I can see the hash by bcrypt is doing its thing and I can see the token being generated in the response after signing up. However as soon as I navigate to a new page after signup or login, for eg the users own id page/user/:userId as per tutorial, I keep getting You need to be logged in. I know I need to check for a token on every request but my question is, why doesn't it seem like the middleware is checking for the token or something is holding it back?
Since the token is shown in the json reponse surely I should be able to check for the tokens existence with the next get request at for eg the /user/:userId page? Isn't that the idea? Or is the browser just showing the response but I still need to actually store it? I don't understand where it goes to so to speak..
Any advice? Or is this a session thing? I know its a bit hard without all the code but if anyone could spot anything relevant so that I could research my next steps I would much appreciate it!
First this middleware in app.js
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.use('/', async (req, res, next) => {
if (req.headers['x-access-token']) {
try {
const accessToken = req.headers['x-access-token'];
const {userId, exp} = await jwt.verify(accessToken, process.env.JWT_SECRET);
console.log('token verified'); // not printing to console
// If token has expired
if (exp < Date.now().valueOf() / 1000) {
return res.status(401).json({
error: 'JWT token has expired, please login to obtain a new one',
});
}
res.locals.loggedInUser = await User.findById(userId);
next();
} catch (error) {
next(error);
}
} else {
next();
}
});
app.use('/', userRoutes);
I have built the roles using the module access-control which is required
const AccessControl = require('accesscontrol');
const ac = new AccessControl();
exports.roles = (function() {
ac.grant('basic')
.readOwn('profile')
.updateOwn('profile');
ac.grant('supervisor')
.extend('basic')
.readAny('profile');
ac.grant('admin')
.extend('basic')
.extend('supervisor')
.updateAny('profile')
.deleteAny('profile');
return ac;
})();
routes examples as per tutorial.
router.get('/signup', (req, res, next) => {
res.render('signup', {
viewTitle: 'User SignUp',
});
});
router.post('/signup', userController.signup);
router.get('/login', (req, res, next) => {
res.render('login', {
viewTitle: 'User Login - WTCT OPS',
});
});
router.post('/login', userController.login );
router.get('/add', userController.allowIfLoggedin, userController.grantAccess('readAny', 'profile'), userController.add);
router.get('/users', userController.allowIfLoggedin, userController.grantAccess('readAny', 'profile'), userController.getUsers);
router.get('/user/:userId', userController.allowIfLoggedin, userController.getUser);
router.put('/user/:userId', userController.allowIfLoggedin, userController.grantAccess('updateAny', 'profile'), userController.updateUser);
router.delete('/user/:userId', userController.allowIfLoggedin, userController.grantAccess('deleteAny', 'profile'), userController.deleteUser);
relevant part of controller
async function hashPassword(password) {
return await bcrypt.hash(password, 10);
}
async function validatePassword(plainPassword, hashedPassword) {
return await bcrypt.compare(plainPassword, hashedPassword);
}
// grant access depending on useraccess role
exports.grantAccess = function(action, resource) {
return async (req, res, next) => {
try {
const permission = roles.can(req.user.role)[action](resource);
if (!permission.granted) {
return res.status(401).json({
error: 'You don\'t have enough permission to perform this action',
});
}
next();
} catch (error) {
next(error);
}
};
};
// allow actions if logged in
exports.allowIfLoggedin = async (req, res, next) => {
try {
const user = res.locals.loggedInUser;
if (!user) {
return res.status(401).json({
error: 'You need to be logged in to access this route',
});
}
req.user = user;
next();
} catch (error) {
next(error);
}
};
// sign up
exports.signup = async (req, res, next) => {
try {
const {role, email, password} = req.body;
const hashedPassword = await hashPassword(password);
const newUser = new User({email, password: hashedPassword, role: role || 'basic'});
const accessToken = jwt.sign({userId: newUser._id}, process.env.JWT_SECRET, {
expiresIn: '1d',
});
newUser.accessToken = accessToken;
await newUser.save();
res.send({
data: newUser,
message: 'You have signed up successfully',
});
} catch (error) {
next(error);
}
};
exports.login = async (req, res, next) => {
try {
const {email, password} = req.body;
const user = await User.findOne({email});
if (!user) return next(new Error('Email does not exist'));
const validPassword = await validatePassword(password, user.password);
if (!validPassword) return next(new Error('Password is not correct'));
const accessToken = jwt.sign({userId: user._id}, process.env.JWT_SECRET, {
expiresIn: '1d',
});
await User.findByIdAndUpdate(user._id, {accessToken});
res.status(200).json({
data: {email: user.email, role: user.role},
accessToken,
});
} catch (error) {
next(error);
}
};
// get one user
exports.getUser = async (req, res, next) => {
try {
const userId = req.params.userId;
const user = await User.findById(userId);
if (!user) return next(new Error('User does not exist'));
// console.log(req.params);
res.send(200).json({
data: user,
});
} catch (error) {
next(error);
}
};
Why when trying to post to the endpoint /user/:userId is the middleware not checking for the token?
Thank you for any advice!
Update:
So far I have tried to removed the / from app.use. I saw I made that mistake now, but also tried removing it from the app.use(userRoutes); middleware to make it apply to all http requests but no luck.
app.use(async (req, res, next) => {
if (req.headers['x-access-token']) {
try {
const accessToken = req.headers['x-access-token'];
const {userId, exp} = await jwt.verify(accessToken, process.env.JWT_SECRET);
// If token has expired
if (exp < Date.now().valueOf() / 1000) {
return res.status(401).json({
error: 'JWT token has expired, please login to obtain a new one',
});
}
res.locals.loggedInUser = await User.findById(userId);
// console.log('Time:', Date.now());
next();
} catch (error) {
next(error);
}
} else {
next();
}
});
app.use(userRoutes);
I also thought that maybe because my server makes http requests in the backend maybe that was causing a problem in setting the x-access-token header? So I tried to change the x-access-token mw to use router.use on all routes but still nothing. I don't understand what I am missing. And just to be sure I'm not missing something fundamental, since I am using the JWT I do not need to use local storage or cookies to allow for browsing between pages while logged in since I can use the token set in the header, correct?
Thanks again for any advice!
That's because your middleware is only tied to the / route. Remove it if you want it to be used for every route. Take a look at the ExpressJS Docs regarding middleware.
I have a login system with tokens. When logging in, it checks whether such a user exists, doesn't have information about current user during the login session. What's the easiest way to check it and send response to frontend?
Routes:
function verifyToken(req, res, next) {
if (!req.headers.authorization) {
return res.status(401).send('Unauthorized request');
}
let token = req.headers.authorization.split(' ')[1];
if (token === 'null') {
return res.status(401).send('Unauthorized request');
}
let payload = jwt.verify(token, 'secretKey');
if (!payload) {
return res.status(401).send('Unauthorized request');
}
req.userId = payload.subject;
next();
}
router.post('/register', (req, res) => {
let userData = req.body;
let user = new User(userData);
user.save((error, registeredUser) => {
if (error) {
console.log(error);
} else {
let payload = { subject: registeredUser._id };
let token = jwt.sign(payload, 'secretKey');
res.status(200).send({ token });
}
})
})
router.post('/login', (req, res) => {
let userData = req.body;
User.findOne({ email: userData.email }, (error, user) => {
if (error) {
console.log(error);
} else {
if (!user) {
res.status(401).send('Invalid email');
} else
if (user.password !== userData.password) {
res.status(401).send('Invalid password')
} else {
let payload = { subject: user._id };
let token = jwt.sign(payload, 'secretKey');
res.status(200).send({ token });
}
}
})
})
You could try using a middleware to retrieve the token from the Authorization header and retrieve the userId from there, the middleware could look something like this:
const decodeToken = (token) => {
return jwt.verify(token, 'secretKey', (err, decoded) => {
if (err) {
return undefined;
}
return decoded;
});
};
const authorize = (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).send({message: 'UNAUTHORIZED'});
}
const token = req.headers.authorization.split(' ')[1];
if (!token) {
return res.status(401).send({message: 'UNAUTHORIZED'});
}
const decodedToken = decodeToken(token);
if (!decodedToken) {
return res.status(401).send({message: 'UNAUTHORIZED'});
}
req.userId = decodedToken.subject;
next();
};
module.exports = authorize;
Hope this helps you, if not, I hope you find your answer :)
EDIT
To use the middleware you only need to add it to your route, I´ll leave you an example with a get request:
const authorize = require('../middleware/authorize');
router.get('/someroute', authorize, (req, res) => {
// authorize will verify the token and attach the userId to your request
// so to use it you'll only need to call req.userId
// something like this
console.log('current logged user id', req.userId);
// YOUR CODE HERE
});
I'm building a small application where a user logs in and gets redirected to /profile. Right now, I fetch the JWT from localstorage and check it via the server. The server then sends it back to the client to tell me if it's a valid session or not.
jQuery/Client:
UserController.initPanel = () => {
if (session === null) {
window.location = "/";
} else {
UserController.requestAuth(session);
}
};
UserController.requestAuth = (sessionToken) => {
var settings = {
"url": "/api/auth",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": `Bearer ${sessionToken}`,
},
"data": ""
}
$.ajax(settings).done(function (response) {
console.log(response);
});
};
Node.js/auth.js route:
router.post("/", (req, res) => {
const authHeader = req.headers.authorization;
if (typeof authHeader !== 'undefined') {
const bearerToken = authHeader.split(' ')[1];
verifyToken(bearerToken, (authData) => {
tokenRequest(authData, (authResponse) => {
handleAuthResponse(req, res, authResponse);
})
});
}
});
const handleAuthResponse = (req, res, authResponse) => {
console.log(authResponse);
return res.status(200).json(authResponse);
}
const verifyToken = (token, cb) => {
jwt.verify(token, 'mysecret', (err, authData) => {
if (err) {
res.sendStatus(403)
} else {
cb(authData);
}
});
}
const tokenRequest = (authHeader, cb) => {
//console.log(authHeader);
var config = {
headers: {'Authorization': `bearer ${authHeader.token}`}
};
axios.get('https://myapi.dev/api/session/me', config)
.then((res) => {
if (res.data.error) {
return response.data
} else {
cb(res.data);
}
})
.catch((error) => {
console.log('error', error);
});
}
I feel like this isn't the correct way to do it. I'm rendering templates with ejs:
router.get("/profile", (req, res) => {
const settings = {
title: "Profile",
revslider: false
};
res.render("profile/profile", { settings: settings } );
});
And if for some reason, JS is disabled, /profile is still accessible. Which isn't that big of a problem, it just feels wrong.
So, is it possible to access /profile route, securely checking for authorization server-side first, before rendering?
Also, auth.js returns some user data I could use in the .ejs template. So that's another reason I'd like to try check auth before rendering as well.
EDIT:
Auth middleware, which I didn't use because I wasn't sure how to pass in the token?
module.exports = (req, res, next) => {
try {
const decoded = jwt.verify(req.body.token, 'mysecret');
req.token = decoded;
} catch (error) {
console.log(error);
return res.status(401).json({
message: 'Auth Failed'
});
}
next();
}
Very basic middleware implementation below which leverages express and express-session.
We basically create a simple function to check req.session exists, within that object, you could have something that identifies whether the user has actually authenticated. I'd recommend you add your own logic here to further check the user status.
const authCheckMiddleware = (req, res, next) => {
// Perform auth checking logic here, which you can attach
// to any route.
if(!req.session) {
return res.redirect('/');
}
next();
};
The authCheckMiddleware can be attached to any route, with app.use or router.use. The req object is passed to all middleware.
// Use the authCheckMiddleware function
router.use('/profile', authCheckMiddleware);
Your router.get('/profile') call is now protected by the above middleware.
// Route protected by above auth check middleware
router.get("/profile", (req, res) => {
const settings = {
title: "Profile",
revslider: false
};
res.render("profile/profile", { settings: settings } );
});
I have a Node.js Express API that doesn't have any security and now I'm looking to patch on JWT-based authorization.
I have it working (using the code below) for one method. Is there an easy way to apply this to all the methods? e.g., maybe using app.all? Or do I have to add the code to every method?
app.get('/people/:id', ensureToken, (req, res, next) => {
var id = req.params.id;
getPerson(id, (err, people) => {
if (err) {
next(err);
return;
}
jwt.verify(req.token, process.env.JWT_KEY, function(err, data) {
if (err) {
res.sendStatus(403);
} else {
res
.status(200)
.set('Content-Type', 'application/json')
.set("Connection", "close")
.send(person);
}
});
});
});
function ensureToken(req, res, next) {
const bearerHeader = req.headers["authorization"];
if (typeof bearerHeader !== 'undefined') {
const bearer = bearerHeader.split(" ");
const bearerToken = bearer[1];
req.token = bearerToken;
next();
} else {
res.sendStatus(403);
}
}
You can do as:
app.use(function(req, res, next){
const bearerHeader = req.headers["authorization"];
if (typeof bearerHeader !== 'undefined') {
const bearer = bearerHeader.split(" ");
const bearerToken = bearer[1];
req.token = bearerToken;
next();
} else {
res.sendStatus(403);
}
});
I'd recommend checking out passport-jwt
You can use this as middleware as well:
app.get('/people/:id', passport.authenticate('jwt', { session: false }), (req, res, next) => { });
The advantage to this over using app.use is that you can specify which routes should be authenticated. i.e., you can exclude a login or registration route. without having to hack in checks like if (req.path == '/login')The other advantage is that using passport you can later add additional authentication methods, should you choose. There's also quite a bit of community support.
I implemented it with app.all, excluding the login route from the check:
app.all(process.env.API_BASE + "*", (req, res, next) => {
if (req.path.includes(process.env.API_BASE + "login")) return next();
return auth.authenticate((err, user, info) => {
if (err) { return next(err); }
if (!user) {
if (info.name === "TokenExpiredError") {
return res.status(401).json({ message: "Your token has expired. Please generate a new one" });
} else {
return res.status(401).json({ message: info.message });
}
}
app.set("user", user);
return next();
})(req, res, next);
});
The full solution here: https://jonathas.com/token-based-authentication-in-nodejs-with-passport-jwt-and-bcrypt/
I am trying to make a GET request to my mLab database. I pass a JWT token with the request and logged it on both the client and server. It reads correctly on the client but shows null on the server. Any help would be much appreciated. I am using Node.js and Angular.
I am pretty new to this, so I apologize in advance if the mistake is obvious.
Here is the server's GET route:
router.get('/', (req, res, next) => {
var decoded = jwt.decode(req.query.token);
console.log(decoded);
console.log('employees');
if(decoded) {
return Company.find({_id: decoded._id})
.populate('user', 'firstName')
.exec(function(err, company) {
if (err) {
return res.status(500).json({
title: 'An error occurred',
error: err
});
}
res.status(200).json({
message: 'Success',
obj: company
});
});
} else {
return res.status(401).json({
title: 'Not authenticated',
error: {
message: 'Please create an account or sign in'
}
});
}
console.log(company);
});
Here is the client:
getEmployees() {
const token = localStorage.getItem('token')
? '?token=' + localStorage.getItem('token')
: '';
console.log(token);
return this.http.get('http://localhost:3000/company' + token)
.map((response: Response) => {
const employees = response.json().obj;
console.log(employees);
let transformedEmployees: Employee[] = [];
for (let employee of employees) {
transformedEmployees.push(new Employee(
employee.firstName,
employee.lastName,
employee.email,
employee.password,
employee.jobTitle,
employee.isAdmin,
employee.tasks
));
}
console.log(transformedEmployees)
this.employees = transformedEmployees;
return transformedEmployees;
})
.catch((error: Response) => {
this.errorService.handleError(error.json());
return Observable.throw(error.json())
});
}
You should NOT be placing your token in the Authorization header of your request.
You use an express middleware to decode the token:
const myDecoder = (req, res, next) => {
req.user = jwt.decode(req.headers.authorization);
next();
}
Then, you place in your route:
router.get('/', myDecoder, (req, res) => {
let user = req.user;
console.log(user);
console.log('employees');
if (user) { blah }
...
Should not be passing the entire token over the URL itself.