I am trying to set up viewpages to show the authenticated user's info such as user's name or email on the page when they are logged in.
To do so, I am using the res.locals function to set the user data at the global level for the pages to access.
const jwt = require("jsonwebtoken")
const User = require("../models/User")
const checkUser = (req, res, next) => {
const token = req.cookies.jwt
if (token) {
jwt.verify(token, "Company_Special_Code", async (err, decodedToken) => {
if (err) {
res.locals.user = null // Set it to null if the user does not exist
next();
} else {
let user = await User.findById(decodedToken.id)
res.locals.user = user
next();
}
})
} else {
res.locals.user = null
next();
}
}
module.exports = {
checkUser
}
The first code, where I call next() function every time the code reaches an endpoint, allows the pages to access the user info without any errors.
However, if I call the next() function only once at the very bottom of the checkUser() function, it causes error claiming that the user is not defined at the view page level. The code is as follows:
const jwt = require("jsonwebtoken")
const User = require("../models/User")
const checkUser = (req, res, next) => {
const token = req.cookies.jwt
if (token) {
jwt.verify(token, "Company_Special_Code", async (err, decodedToken) => {
if (err) {
res.locals.user = null // Set it to null if the user does not exist
} else {
let user = await User.findById(decodedToken.id)
res.locals.user = user
}
})
} else {
res.locals.user = null
}
next();
}
module.exports = {
checkUser
}
If I coded the function correctly, the checkUser() function should get to the next() function at the bottom regardless of the status of jwt token or if there was an error during the token verification process. I would really appreciate your help if you can tell me what I am getting it wrong here...
Your jwt.verify is has an asynchronous callback and next() at the bottom is being called before that returns. So you either need to put next() into that callback, or use jsonwebtoken synchronously. Something like this:
const checkUser = (req, res, next) => {
const token = req.cookies.jwt
if (token) {
try {
const decodedToken = jwt.verify(token, "Company_Special_Code")
// This only runs if the token was decoded successfully
let user = await User.findById(decodedToken.id)
res.locals.user = user
} catch (error) {
res.locals.user = null // Set it to null if the user does not exist
}
} else {
res.locals.user = null
}
next();
}
When you use an async callback like that, javascript will continue processing the rest of the script while that callback is running on the side (more or less). So next() is being called unaware of the need to wait for the callback or anything it might handle.
Your validation part misses the correct error handling in middleware. If token is invalid, then why should user get access to controller, you can send error from middleware itself. If you are not sending error from middleware and calling next(), then will defeat purpose of your authentication middleware.
Update your code as follows,
const jwt = require("jsonwebtoken")
const User = require("../models/User")
// The routes, which does not requires aurthentication
const usecuredRoutes = []
const checkUser = (req, res, next) => {
if(usecuredRoutes.indexOf(req.path) === -1){
const token = req.cookies.jwt
if (token) {
jwt.verify(token, "Company_Special_Code", async (err, decodedToken) => {
if (err) {
res.locals.user = null // Set it to null if the user does not exist
// Token is invalid, then telll user that he is don't have access to the resource
res.status(403).send('Unauthorized')
} else {
let user = await User.findById(decodedToken.id)
res.locals.user = user
next();
}
})
} else {
// Token does not exists, then telll user that he is don't have access to the resource
res.status(403).send('Unauthorized')
}
} else {
next()
}
}
module.exports = {
checkUser
}
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 have an auth protect middleware that checks if req.params.id === req.userId(the one returned by bcrypt verify function). I have a protect function which upon bcrypt.verify returns the decoded.id.
The Id returned from req.user._id despite being the same as decoded.id returns "not authorized in the verifyandAuth middleware, however, if I replace req.user._id by decoded.id(in verifyandAuth), the if function works and the middleware goes through without giving the "not authorized error". Can anybody please tell me why that's happening? (req.user._id and decoded.id upon console.log show the same id, as such, there's no mistake there).
Protect Middleware
export const protect = async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer")
)
try {
{
token = req.headers.authorization.split(" ")[1];
const decoded = jwt.verify(token, "kris");
req.userId = decoded.id;
req.user = await User.findById(decoded.id).select("-password");
next();
}
} catch (error) {
res.status(400).json(error.message);
}
if (!token) {
return res.status(400).json("Invalid Token");
}
};
auth Middleware
export const verifyandAuth = (req, res, next) => {
protect(req, res, () => {
console.log(req.user._id, req.params.id);
if (req.user._id === req.params.id || req.isAdmin) {
next();
} else {
res.status(400).json("Not authorised");
}
});
};
Learning about the concept of microservices in Nodejs, I have set up two microservices auth and users, both standalone and running on different ports.
The auth service handles user creation and log-in users using a username and password. This works as expected. I've used jwt to generate the tokens. I can easily create a user, create a session token and verify its validity.
My second service, users, I intend to use to show greetings to users and fetch a user's detail. I need to use the auth service to know when a user is logged in in this setting.
However, with my current workings, when I try to go to an endpoint, /users/:id/sayhello with a valid user id and a valid token passed in the headers, I get the following errors:
TypeError: Cannot read properties of undefined (reading 'id') at /path/auth/verifyToken.js:23:21 .
And then this; from jwt:
{
"name": "JsonWebTokenError",
"message": "secret or public key must be provided"
}
Let's look at my setup now.
Here's my verifyToken.js file from the auth service:
const verifyToken = (req, res, next)=>{
const authHeader = req.headers.token
// split the header, and get the token "Bearer token"
const token = authHeader.split(" ")[1];
if (authHeader) {
jwt.verify(token, process.env.JWT_SEC, (err, user)=>{
if (err) res.status(403).json(err);
req.user = user
next();
})
} else {
return res.status(401).json("You are not authenticated")
}
}
const verifyTokenAndAuthorization = (req, res, next) =>{
verifyToken(req, res, ()=>{
if(req.user.id === req.params.id){ // error traced back to this line
next();
}else{
res.status(403).json("Permission denied!");
}
})
}
From my users service, here's the code that uses the auth service to know when the user is logged in then say hello.
app.get("/users/:id/sayhello", verifyTokenAndAuthorization, async (req, res) => {
try {
const user = await User.findById(req.params.id);
console.log(req.params.id) // undefined
res.status(200).json(`Hello ${user.username}`);
} catch (error) {
res.status(500).json(error);
}
});
I've with no success sought any leads from similar posts like A,B and C
I'm not sure of what's not right. I'll appreciate possible suggestions and leads towards a fix.
console.log(process.env.JWT_SEC)
The authentication process got failed, so the user property was unset on the req object, so req.user is null.
Ensure the integrity of your inputs.
I think in the Headers convention of using Authorization or authorization key will not dissappoint as its the most preferred way of doing this have something like
I have done a rolebased approach in tackling this so check the implementation as of the question rolebased structure.
What to check for
Check if the Authorization header is available
If it does not exist just throw an exception or a response with some error
Check if the token is present in the Bearer token
If the token is not there just throw an exception to temnate the excecution
If the AuthHeader and token are present then now you can be certain that you have the token, thus you can just return the jwt.verify(...args:[])
Depending on validity everything here is on check
If the jwt is valid then the JWT payload is there thn we can pass it to the request object to carry it through to the other middlewares
If we want to now have Athorization then we override the next parameter with a function to execute on it`s behalf
From here now you can check on the user Roles and return next based on what permissions they have.
import RoleModel from "../models/RoleModel"
import UserModel from "../models/UserModel"
class AuthMiddleware {
constructor(role:typeof Model, user:typeof Model) {
this.role=role
this.user=user
}
verifyJwt = async (req, res, next) => {
try {
const AuthHeader = req.headers["authorization"]
if (!AuthHeader) {
return res.status(401).json("Please provide an auth token")
}
const token = AuthHeader.split(" ")[1]
if (!token) {
return res.status(401).json("Please provide an auth token")
}
return jwt.verify(token, SECRET_KEY, async (error, payload) => {
if (error) {
return res.status(401).redirect("/auth/login")
}
const decodedPayload = payload as JWTPayloadType
req.user = decodedPayload
return next()
})
} catch (error) {
return next(error)
}
}
loginRequired = async (req, res, next) => {
try {
this.verifyJwt(req, res, async () => {
const user = await this.user.findById(req.user.userId)
const role = await this.role.findById(user.role)
const permitted = await role.hasPermission(Permissions.USER)
if (!permitted) {
return res.status(403).json("Forbidden")
}
return next()
})
} catch (error) {
return next(error)
}
}
adminRequired = async (req, res, next) => {
try {
this.verifyJwt(req, res, async () => {
const user = await this.user.findById(req.user.userId)
const role = await this.role.findById(user.role)
const permitted = await role.hasPermission(Permissions.ADMIN)
if (!permitted) {
return res.status(403).json("Forbidden")
}
return next()
})
} catch (error) {
return next(error)
}
}
}
export default new AuthMiddleware(RoleModel, UserModel)
Applying this to a middleware
import auth from "../middlewares/AuthMiddleware"
/**
* ************* UPDATE USER PROFILE ********
*/
router
.route("/update/profile/:id")
.put(
auth.loginRequired,
imageUpload.single("profile"),
uController.updateUserDetails,
uMiddleware.uploadProfilePic,
)
Assuming you supply the middlewares to the given route its easy to abstract away the verify jwt and have a login_required based on the roles you want achieved.
Full implementation of this I have on this Github repo Github link
I am using JWT token authentication in my Node.js and express application. But for each route I am calling the method to verify the token and my code is given below.
route.js:
const express = require('express'),
controller = require('../controller/customer.controller'),
verify = require('../utill/verify.util.js'),
Router = express.Router();
class DemoProjectRouter {
getRouter() {
try{
Router.get('/', verify.verifyToken, controller.getCustomer.bind(controller));
Router.post('/add',verify.verifyToken, controller.addCustomer.bind(controller));
return Router;
}catch(error) {
console.log(error);
}
}
}
module.exports = new DemoProjectRouter();
In the below file I am verifying the token.
const jwt = require('jsonwebtoken');
const _ = require('lodash');
const jwtKey = "my_secret_key"
const jwtExpirySeconds = '2d';
class DemoProjectJWT {
async createJWT(username) {
try{
let obj = {};
obj['username'] = username;
const token = jwt.sign(obj, jwtKey, {algorithm: "HS256", expiresIn: jwtExpirySeconds});
return token;
}catch(error){
console.log(error);
}
}
async verifyToken(req, res, next) {
try{
let token = '';
if (_.get(req,['body', 'token'])) {
token = req.body.token;
}
if (_.get(req,['query', 'token'])) {
token = req.query.token;
}
if (_.get(req,['headers', 'x-access-token'])) {
token = req.headers['x-access-token'];
}
if (_.get(req,['cookies', 'token'])) {
token = req.cookies.token;
}
if (token === '' || token === null) {
let err = new Error('No token provided!');
err.status = 403;
res.send(err);
}else{
jwt.verify(token, jwtKey, (err, decode) => {
if (err) {
if (err.name === 'TokenExpiredError') {
console.log("token expired!");
let err = new Error('You are not authenticated!');
err.status = 401;
res.send(err);
}
}else{
req.decoded = decode;
next();
}
})
}
}catch(error) {
console.log(error);
}
}
}
module.exports = new DemoProjectJWT();
Here I am binding my token in each route method but I need to write any common method where it will include the token in each route for verification so that if I am creating any new route method I will not add again verify.verifyToken for verification.for each user request it should call automatically.
You need to register your token verification handler as a router-level middleware:
Router.use(verify.verifyToken);
Router.get('/', controller.getCustomer.bind(controller));
Router.post('/add', controller.addCustomer.bind(controller));
You can do it with the app level middleware.
But make sure to put appropriate logic within that middleware, since it will get called for all the routes.
app.use('*', (req, res, next) => {
if (verify.verifyToken()) {
next();
} else {
// Do something else
}
})
I set up jsonwebtoken in node.js and testing it with postman. First upon login I generate the token, then I copy it and use for the post request to a protected route. And jwt.verify runs just fine, the correct userId is retrieved, but when I call next() (see below) it goes to 404 error handler instead of the index where the protected routes are located:
var index = require('./routes/index')
function verifyToken (req, res, next) {
// Get auth header value
const bearerHeader = req.headers['authorization']
// check if bearer is undefined
const message = 'Unauthorized user, access denied!'
if (typeof bearerHeader !== 'undefined') {
const bearerToken = bearerHeader.split(' ')[1]
jwt.verify(bearerToken, 'privateKey', (err, userData) => {
if (err) {
// Forbidden
console.log(err)
res.status(403).send({ err })
} else {
console.log(userData.userId)
req.userId = userData.userId
------> next()
}
})
} else {
// Forbidden
res.status(403).send({ message })
// res.sendStatus(403)
}
}
// app.use('/auth', index);
app.use('/auth', verifyToken, index)
Why is it happening? What am I doing wrong here? Basically my goal is to set userId on the req object, then get it back in the root route auth/ in index