I want that I can access a router with or without a token. If user has a token, than give me req.user
Like this:
router.get('/profile', function(req, res) {
if(req.user) { // or if (req.isAuthenticated())
res.send('logged')
} else {
res.send('not loggedIn')
}
});
My app:
var JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
opts.secretOrKey = 'sh';
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
User.findOne({id: jwt_payload.sub}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
// or you could create a new account
}
});
}));
If I try to access /profile without a token, works fine.
But, when a try to access /profile with a token in header give me not loggedIn
I want to access it even without a token and, if I provided a toke, give me the user.
ps: Already tested using passport.authenticate('jwt') in route and works. If I give token let me access, if not give me unauthorized.
This is how I'm doing it:
const passport = require('passport');
const optionalJwt = function (req, res, next) {
if (req.headers['authorization']) {
return passport.authenticate('jwt', { session: false })(req, res, next);
}
return next();
};
app.get('/my/route', optionalJwt, function (req, res, next) {
if (req.isAuthenticated()) {
res.send('My name is ' + req.user.name);
} else {
res.send('I\'m not authenticated :(');
}
});
It will still fail if the auth header is included but the token has expired or it's invalid, but it might serve the purpose. For me it did.
Change you router as follows
router.get('/profile', authenticateUser(), profile());
function authenticateUser(req, res, next) {
// your strategy here...
if (authenticated) {
req.user = user;
next();
} else {
return res.status(401).send("Not authenticated");
}
}
function profile(req, res, next) {
var userId = req.user.id;
User.findById(userId, function(err, user) {
if (err) { return res.status(500).json(err); }
return res.json(user);
})
}
you should be using one of the below to access request data
if (req.params.user) {do something...}
or
if (req.body.user) {do something...}
the request object has no user attribute.
Related
In verifyAdmin function the verifyToken is passed where with the help of jwt the user property is added to the request and then using that property's key isAdmin we can check if the user is admin or not but in the below given code it is not working. Also same issue is being faced for the verifyUser.
import jwt from "jsonwebtoken";
import { createError } from "../utils/error.js";
// This verifies that is the token correct i.e. weather person is admin or not
export const verifyToken = (req, res, next) => {
const token = req.cookies.access_token;
if (!token) {
return next(createError(401, "You are not authenticated!"));
}
jwt.verify(token, process.env.JWT, (err, user) => {
if (err) return next(createError(403, "Token is not valid!"));
// Here in place of the req.user you can write any property as nothing is defined in request
req.user = user;
console.log(req.user);
next();
});
};
// To verify the user
export const verifyUser = (req, res, next) => {
// If the user have the token i.e. user needs to be authenticated.
verifyToken(req, res, next, () => {
// If the user id matches or user is admin then CRUD operations can be performed.
if (req.user.id === req.params.id || req.user.isAdmin) {
next();
} else {
return next(createError(403, "You are not authorized!"));
}
});
};
export const verifyAdmin = (req, res, next) => {
verifyToken(req, res, next, () => {
console.log(`Before or After Token`);
if (!req.user.isAdmin) {
next();
} else {
return next(createError(403, "You are not authorized!"));
}
});
};
Here the user details are perfectly verified till verifyToken when passed from verifyAdmin but then it is not checking for the admin or user in verifUser function.
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.
In my route's file I have
const express = require("express");
const { body, check, validationResult } = require("express-validator/check");
const passport = require("passport");
const User = require("../models/User");
which perfectly works with my registration route
router.post("/register",
check('username').isEmail().withMessage("You need email for username registration"),
check("password").custom((value, { req }) => {
if (value !== req.body.password2) {
throw new Error("Password confirmation failed")
} else {
return true;
}
}),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
errors.array().forEach(err => req.flash('error', err.msg));
return res.redirect('/register');
}
const newUser = new User({ username: req.body.username });
User.register(newUser, req.body.password, (err, user) => {
if (err) {
console.log(err);
return res.redirect('back');
}
passport.authenticate('local')(req, res, () => {
res.redirect('/blogs');
})
});
});
but I can't implement that logic in login passport route, so I basically
want to know how to put express-validator in passport login logic
router.post("/login", check('username').isEmail(), passport.authenticate("local", {
failureRedirect: "/login"
}), function (req, res) {
req.flash('success', 'You are successfuly Sign In');
res.redirect("/blogs");
});
Here is what works perfectly for me.
router.post("/login",
[check('username').isEmail().withMessage("username should be an email")],
(req, res, next) => {
// Check validation.
const errors = validationResult(req);
if (!errors.isEmpty()) {
errors.array().forEach(err => req.flash('error', err.msg));
return res.redirect('/login');
}
next();
},
// If validation succeeds, go on with passport authentication logic.
passport.authenticate("local", {...
...
});
);
You have to place the validation logic inside an array according to the docs, and you should do the same with the register route.
I downloaded one sample project from internet. Below there are some fragments of the code:
On the routes file I have the following (just a fragment):
var authController = require('./controllers/authController'),
var passport = require('passport');
var authLoginFacebook =
passport.authenticate(
'facebook',
{
session: false,
scope: ['public_profile', 'email']
}
);
var checkJwt = function(req, res, next) {
passport.authenticate(
'jwt',
{session: false },
function (err, user, info) {
next();
}
)(req, res, next);
}
module.exports = function(app) {
// ...
app.get(
'/api/auth/login/facebook/callback',
checkJwt,
authLoginFacebook,
authController.login
);
// ...
}
On the passport file I have the following (just a fragment):
var User = require('../models/user');
var credentials = require('./credentials');
var JwtStrategy = require('passport-jwt').Strategy;
var ExtractJwt = require('passport-jwt').ExtractJwt;
var LocalStrategy = require('passport-local').Strategy;
var FacebookStrategy = require('passport-facebook').Strategy;
module.exports = function(passport) {
passport.use(
new JwtStrategy({
secretOrKey: credentials.secret,
jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('JWT'),
},
function(payload, done) {
User.findById(
payload._id,
function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
}
}
);
}
)
);
var fbStrategy = credentials.facebook;
fbStrategy.passReqToCallback = true;
passport.use(new FacebookStrategy(fbStrategy,
function(req, token, refreshToken, profile, done) {
// asynchronous
process.nextTick(function() {
// check if the user is already logged in
if (!req.user) {
User.findOne({
'facebook.id': profile.id
}, function(err, user) {
if (err)
return done(err);
if (user) {
// if there is a user id already but no token (user was linked at one point and then removed)
if (!user.facebook.token) {
user.facebook.token = token;
user.facebook.name = profile.name.givenName + ' ' + profile.name.familyName;
user.facebook.email = (profile.emails[0].value || '').toLowerCase();
user.save(function(err) {
if (err)
return done(err);
return done(null, user);
});
}
return done(null, user); // user found, return that user
} else {
// if there is no user, create them
var newUser = new User();
newUser.facebook.id = profile.id;
newUser.facebook.token = token;
newUser.facebook.name = profile.name.givenName + ' ' + profile.name.familyName;
newUser.facebook.email = (profile.emails[0].value || '').toLowerCase();
newUser.save(function(err) {
if (err)
return done(err);
return done(null, newUser);
});
}
});
} else {
// user already exists and is logged in, we have to link accounts
var user = req.user; // pull the user out of the session
user.facebook.id = profile.id;
user.facebook.token = token;
user.facebook.name = profile.name.givenName + ' ' + profile.name.familyName;
user.facebook.email = (profile.emails[0].value || '').toLowerCase();
user.save(function(err) {
if (err)
return done(err);
return done(null, user);
});
}
});
})
);
// ...
};
I have few questions here:
why on: passport.authenticate('jwt', ... are passed these arguments: (req, res, next) and on passport.authenticate('facebook', ... don't while they are used in the same line one next to other?
app.get(
'/api/auth/login/facebook/callback',
checkJwt,
authLoginFacebook,
authController.login
);
If I remove those arguments, then the web page keeps loading indefinitely.
why inside: passport.use(new FacebookStrategy is defined: req.user? where was declared the field: user for the object req?
Thanks!
*Edit: this is a function invoking another function...which is required because of the callback using next(). The facebook function doesn't have that.
In other words, when you invoke passport.authenticate, the return value is actually going to be a function expecting the parameters req, res, next. Normally you don't need to wrap it, because it just works. However, in this case there is a callback function being passed in as an argument, and that callback function needs access to the next parameter. So you have to wrap the whole thing to gain access to that next parameter.
*Note: the wrapped function isn't actually returning anything to/through the wrapping function. passport.authenticate() returns a function, and then this return function is self-invoked with the parameter group that follows. But this second self-invoked function result isn't captured as a variable or returned or anything.
The reason is that the important thing is either sending a response using the res parameter or allowing express to continue to the next layer of middleware/etc by calling the next() callback parameter. It's all happening asynchronously, and flow is directed using the callbacks.
var checkJwt = function(req, res, next) {
passport.authenticate(
'jwt',
{session: false },
function (err, user, info) {
next();
}
)(req, res, next);
}
the req.user would be a user from a previous login, which i believe passport.js normally stores using the express-session package.
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/