I have a express nodejs backend which has three URL functions in which
1) registerUser() added user details to database and provided a JWT for the caller
2) verifyToken()- verifies if the JWT is valid
3) getConfiguration()- if JWT is verified from above function provides user with some configuration data
So the express code I'm using to achieve this is
//Routes.js
app.use(requestIp.mw())
app.route('/register')
.post(userController.registerUser);
app.use(userController.verifyToken)
app.route('/user/configuration')
.post(chayakkadaController.getConfiguration);
Now my issue is whenever I try calling the URL /register instead of calling registerUser function it calls verifyToken and says my token is invalid ( I want registerUser function to work without token, but getConfiguration should work only with token)
This is my verifyToken function
export function verifyToken(req, res, next) {
var token = req.body.token || req.headers["token"];
var appData = {};
if (token) {
jwt.verify(token, process.env.SECRET_KEY, function (err, decoded) {
if (err) {
appData["status"] = 1;
appData["error"] = "Invalid Token";
res.status(500).json(appData);
} else {
req.user = decoded;
next();
}
});
} else {
appData["status"] = 1;
appData["error"] = "Need access token";
res.status(403).json(appData);
}
}
My register User code
export function registerUser(req, res) {
let userData = {
device: req.body.device,
device_version: req.body.device_version,
device_id: req.body.device_id,
app_version: req.body.app_version,
app_id: 2,
ip_address: req.headers['x-real-ip'] || req.connection.remoteAddress
}
database.query(`INSERT INTO users SET ?`, userData)
.then(result => {
let user = {
id: result.insertId
}
let token = jwt.sign(user, process.env.SECRET_KEY);
let appData = {};
appData["token"] = token;
redis.sendMessage({
qname: 'registration_queue',
message: result.insertId + '',
}, (err, resp) => {
res.status(201).json(appData);
});
})
.catch(err => {
console.log(err);
res.status(500).json("Database Error");
})
}
Why you wanna to invent the wheel? there is a NPM module for that:
express-jwt
It has middleware that checks the jwt, if it valid, it decodes the payload and adds it to the request after that it proceed to your controller, if it is not valid, it throws an error, that you should catch, and do what ever you want.
It has the unless feature, so you can configure the entire subpath as restricted unless it is /register
router.use(`admin/`, [
expressJwt({ secret: jwtSecret }).unless({
path: ['/register]
}),
]);
Related
I am currently stuck trying to implement a refresh token with express middleware, react and JWT. The problem I am having is I need to pass the refreshed token back to the client from the middleware function. I have tried using res.locals.variableName and also res.set, but once the middleware function is finished and next() is called, I am responding with res.json in my route, which I think is overwriting anything I set in the response from the middleware. How can I return this refresh token to client side while still being able to call next()?
app.all('*', function (req, res, next) {
const headerToken = req.headers.token;
const refreshToken = req.headers.refreshtoken;
const isVerifiedPath = verifyPaths(unauthorizedPaths, currentPath);
if (isVerifiedPath) {
next()
}
else {
jwt.verify(headerToken, process.env.KEY, async (err, data) => {
if (err) {
if (err.expiredAt) { // expired web token
jwt.verify(refreshToken, process.env.KEY, async (err, data) => {
if (data) {
const User = require('./models/User');
const user = await User.query().findById(data.user.id);
const token = jwt.sign({ user }, process.env.KEY, { expiresIn: 5 });
req.user = user;
res.locals.varName = token; // I would like this to be accessible from the response my api returns
next();
}
})
}
else {
return res.status(401).json({ err: 401 });
}
}
else {
req.user = data.user;
next();
}
});
}
});
The solution to my problem was to pass the token through headers like this. I wasn't able to view the token client side because I was not exposing the header (line 2).
res.set('x-token', token);
res.set('Access-Control-Expose-Headers', 'x-token');
Hello can some help with this, I can't seems to figure out why I always got en error TokenExpiredError: jwt expired I just created the token.
Here's what I wanted to do.
1.) when user logged in, response contains the accessToken and refreshToken
2.) call the auth/token every 30 seconds to generate new accessToken.
auth/token api will accept refresh_token as request data and the accessToken as Bearer Authorization, however in I always got jwt expired. It fails on the authentication middleware.
However when I used the accessToken I got from logged in api, I got jwt expired error.
JWT Service
sign(payload, expiresIn = '15m') {
return jwt.sign(payload, this.privateKey, {
algorithm: 'RS256',
expiresIn,
});
}
verify(token) {
return jwt.verify(token, this.publicKey, { algorithms: ['RS256'] });
}
Controller.js
// login api
login(req, res, next) {
const { body: { email, password } } = req;
this.accountService.findByEmail(email)
.then(async (data) => {
const accessToken = this.jwtService.sign({ data}, '2m');
const refreshToken = this.jwtService.sign({ data}, '1h');
return res.send(new SuccessResponse(200, {accessToken, refreshToken}));
})
.catch((error) => {
next(error);
});
}
}
// auth/token api
authToken(req, res, next) {
const { body: { refresh_token } }= req;
const payload = { refresh_token };
const newAccessToken = this.jwtService.sign({ payload }, '1m', 'RS256');
}
authenticate.middleware.js
export const authenticate = (req, res, next) => {
const authorizationBearer = req.get('authorization');
const accessToken = authorizationBearer.replace('Bearer ', '');
const decodedData = jwtService.verify(accessToken);
if (decodedData) {
next();
}
}
Did I miss something or did I do anything wrong?
Library Used:
jsonwebtoken: 8.5.1
express: 4.17.1
I am trying to complete jwt sign in I am trying to create the jwt token in login.Then I am trying to use it within my user-questions route.
I am using a react front end.
Is this the correct way to do so?
I am currently getting error
const token = req.cookies.auth;
[0] ^
[0]
[0] ReferenceError: req is not defined
Below is my routes login code which assigns the token once the my sql server return that the values for email and password exist. User-questions tries to use this jwt. and I have also included how the token is verfied in a function
Verfiy users
app.get("/user-questions", verifyToken, function(req, res) {
app.use(function(req, res, next) {
// decode token
if (token) {
jwt.verify(token, "secret", function(err, token_data) {
if (err) {
console.info("token did not work");
return res.status(403).send("Error");
} else {
req.user_data = token_data;
sql.connect(config, function(err) {
if (err) console.log(err);
// create Request object
var request = new sql.Request();
// query to the database and get the records
request.execute("dbo.ViewQuestions", function(err, recordset) {
if (err) console.log(err);
// send records as a response
res.json(recordset);
next();
});
});
}
});
} else {
console.info("no token");
console.log("no token");
return res.status(403).send("No token");
}
});
});
Login route
app.post("/login", async (req, response) => {
try {
await sql.connect(config);
var request = new sql.Request();
var Email = req.body.email;
var Password = req.body.password;
console.log({ Email, Password });
request.input("Email", sql.VarChar, Email);
request.input("Password", sql.VarChar, Password);
var queryString =
"SELECT * FROM TestLogin WHERE email = #Email AND password = #Password";
//"SELECT * FROM RegisteredUsers WHERE email = #Email AND Password = HASHBYTES('SHA2_512', #Password + 'skrrt')";
const result = await request.query(queryString);
if (result.recordsets[0].length > 0) {
console.info("/login: login successful..");
console.log(req.body);
token = jwt.sign(
{ Email },
"secretkey",
{ expiresIn: "30s" },
(err, token) => {
res.json({
token
});
res.cookie("auth", token);
res.send("ok");
}
);
} else {
console.info("/login: bad creds");
response.status(400).send("Incorrect email and/or Password!");
}
} catch (err) {
console.log("Err: ", err);
response.status(500).send("Check api console.log for the error");
}
});
Verify users
// Verify Token
function verifyToken(req, res, next) {
// Get auth header value
const bearerHeader = req.headers["authorization"];
// Check if bearer is undefined
if (typeof bearerHeader !== "undefined") {
// Split at the space
const bearer = bearerHeader.split(" ");
// Get token from array
const bearerToken = bearer[1];
// Set the token
req.token = bearerToken;
// Next middleware
next();
} else {
// Forbidden
res.sendStatus(403);
}
}
Please advise if this in theory should work. And if not please advise how to resolve.
EDIT :
The error has been resolved however now simply my jwt tokens to do not work. As when logged in and I manually route to user-questions it does not load the component and within the console it says 403 not available (this is set in the code when the jwt token is not working).
UPDATE:
How would I include
['authorization'] = 'Bearer ' + token;
into
handleSubmit(e) {
e.preventDefault();
if (this.state.email.length < 8 || this.state.password.length < 8) {
alert(`please enter the form correctly `);
} else {
const data = { email: this.state.email, password: this.state.password };
fetch("/login", {
method: "POST", // or 'PUT'
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",
},
body: JSON.stringify(data)
})
// .then(response => response.json())
.then(data => {
console.log("Success:", data);
})
.catch(error => {
console.error("Error:", error);
});
}
}
There are a couple of errors with your code:
In your /login route:
You are trying to set the "auth" cookie after the response is being sent
You are trying to send a response twice, once via res.json and once via res.send
You are assigning to a token variable that no longer exists (token = jwt.sign(...))
In your verifyToken method:
This method is only verifying that the request has a token set, it's not validating or decoding it. I would consider moving your jwt.verify() call to this method.
In your /user-questions route:
You're calling app.use inside of app.get, when both of these are intended to be called at the root level. Remove your app.use call.
You need to grab token from the request, ex. const { token } = req;
You are sending a response via res.json(), but you are still calling next() afterwards. From the Express docs:
If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function.
This is how I would make these changes:
/login route:
app.post("/login", async (req, response) => {
try {
await sql.connect(config);
var request = new sql.Request();
var Email = req.body.email;
var Password = req.body.password;
console.log({ Email, Password });
request.input("Email", sql.VarChar, Email);
request.input("Password", sql.VarChar, Password);
var queryString =
"SELECT * FROM TestLogin WHERE email = #Email AND password = #Password";
//"SELECT * FROM RegisteredUsers WHERE email = #Email AND Password = HASHBYTES('SHA2_512', #Password + 'skrrt')";
const result = await request.query(queryString);
if (result.recordsets[0].length > 0) {
console.info("/login: login successful..");
console.log(req.body);
jwt.sign(
{ Email },
"secretkey",
{ expiresIn: "30s" },
(err, token) => res.cookie('auth', token).json({ token })
);
} else {
console.info("/login: bad creds");
response.status(400).send("Incorrect email and/or Password!");
}
} catch (err) {
console.log("Err: ", err);
response.status(500).send("Check api console.log for the error");
}
});
verifyToken method:
// Verify Token
function verifyToken(req, res, next) {
// Get auth header value
const bearerHeader = req.headers["authorization"];
// Check if bearer is undefined
if (typeof bearerHeader !== "undefined") {
// Split at the space
const bearer = bearerHeader.split(" ");
// Get token from array
const bearerToken = bearer[1];
// verify the token and store it
jwt.verify(bearerToken, "secret", function(err, decodedToken) {
if (err) {
console.info("token did not work");
return res.status(403).send("Error");
}
// Set the token
req.token = bearerToken;
req.decodedToken = decodedToken;
next();
});
} else {
// Forbidden
res.sendStatus(403);
}
}
/user-questions route:
app.get("/user-questions", verifyToken, function(req, res) {
// if a request has made it to this point, then we know they have a valid token
// and that token is available through either req.token (encoded)
// or req.decodedToken
sql.connect(config, function(err) {
if (err) console.log(err);
// create Request object
var request = new sql.Request();
// query to the database and get the records
request.execute("dbo.ViewQuestions", function(err, recordset) {
if (err) console.log(err);
// send records as a response
res.json(recordset);
});
});
});
In order to secure REST API I'm using middleware to check for user's JWT token and only allow that particular user to access his own data.
In auth.js
const jwt = require('jsonwebtoken')
const User = require('../models/user')
const auth = async (req, res, next) => {
try {
const token = req.header('Authorization').replace('Bearer ', '')
const decoded = jwt.verify(token, process.env.JWT_SECRET)
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })
if (!user) { // If no user is found
throw new Error()
}
// if there's a user
req.token = token
req.user = user
next()
} catch (e) {
res.status(401).send({ error: 'Please authenticate.' })
}
}
module.exports = auth
In one of the get/update router
router.get('/itemShipmentStatus', auth, async (req, res) => {
// Get the items shipment status from db.
})
However, I've noticed I need to create a new admin user (e.g. admin 1, admin2) to get and update the itemShipmentStatus for all the users. Is there a way to achieve user group authentication through the middleware (auth.js?)
Update:
The only solution I can think of is to add another "userGroup" field to the user document when creating a new user. Then in the middleware auth.js add in another condition to check if the user belongs to the admin group.
if (!user || user.userGroup !== 'Admin') { // If no user is found
throw new Error()
}
Is this the conventional way of doing it?
I would suggest adding permissions array stored in the user. That way you are more flexible. Then
const auth = (allowedPermission) => (async (req, res, next) => {
try {
const token = req.header('Authorization').replace('Bearer ', '')
const decoded = jwt.verify(token, process.env.JWT_SECRET)
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })
if (!user) { // If no user is found
throw new Error()
}
if (!user.permissions.includes(allowedPermission)){
throw new Error() // Forbidden 403
}
// if there's a user
req.token = token
req.user = user
next()
} catch (e) {
res.status(401).send({ error: 'Please authenticate.' })
}
})
and in the route
router.get('/itemShipmentStatus', auth([admin, user]), async (req, res) => {
// Get the items shipment status from db.
})
Then it would be a matter to identify the correct code to run.
I would suggest considering the division of the api. A public api and an admin api. This is because conceptually a user may want to be admin and access its own itemShipmentStatus. So having
router.get('/itemShipmentStatus', auth([admin, user]), async (req, res) => {
// Get the items shipment status from db.
})
router.get('/admin/itemShipmentStatus', auth([admin]), async (req, res) => {
// Get the items shipment status from db of all user.
})
This allows an admin user to test the API as a normal user and get all the status as an admin.
A more conventional way of doing this would be to create an AuthRouter which extends the default express.Router and checks for allowed roles, so there will be no need to use middleware for each route.
Extending express.Router to check for roles:
const express = require('express');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
export default class AuthRouter extends express.Router {
addRoles(...roles) {
return this.use(checkAccessForRoles(...roles));
}
}
const checkAccessForRoles = (...roles) => async (req, res, next) => {
const token = req.header('Authorization').replace('Bearer ', '');
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token });
if (!roles.includes(user.role)) throw new Error('Forbidden');
req.user = user;
return next();
};
Using AuthRouter for ADMIN user role:
const adminRouter = new AuthRouter({
prefix: '/admin',
})
.addRoles(['ADMIN']);
adminRouter.get('/itemShipmentStatus', async (req, res) => {
// Get the items shipment status from db.
});
I have a really big problem with security in my web application.
I implemented JWT token when user login to my application (REST API returns token).
In my jwt token, I have only userID. Problem is that, when I would like to login on user with ID = 1,
I can see and execute rest actions from all other users with the same token. for example:
When I looged userId = 1, I doing GET action: /api/users/1 and I have a information about user 1. But I can doing action /api/users/2, 3 etc.
All with one token. how to secure it?
const jwt = require('jsonwebtoken');
const env = require('../config/env.config.js');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, env.SECRET_KEY);
req.userData = decoded;
next();
} catch (error) {
return res.status(401).json({
message: 'Auth failed',
});
}
};
I think the best solution would be to create middleware that check the id of the sender and attach it to routes, similar to bellow
const middleware = (req, res, next) => {
const id = req.params.id || req.body.id || req.query.id
if (req.userData.id === id) {
next()
} else {
res.status(403).send({message: "forbidden"})
}
}
router.get("/api/users/:id", middleware, (req, res) => {
// do your staff
res.send({message: "ok"})
})
router.put("/api/users/:id", middleware, (req, res) => {
// do your staff
res.send({message: "ok"})
})