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
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');
I'm trying to make a fetch request to my back-end route, but it seems cookie-parser isn't actually parsing the data? The authorization header is getting through successfully as well. Here's my fetch request:
async componentDidMount() {
const res = await fetch(`http://localhost:8080/tweets`, {
headers: {
Authorization: `Bearer ${this.context.userToken}`,
}
})
const tweets = await res.json();
console.log(tweets)
}
It's hitting the route just fine, and if I disable my auth middleware I do get the data, but when I try to console.log my token, it's just undefined. req.cookies is just a null object. Here's my auth middleware:
const restoreUser = (req, res, next) => {
// token being parsed from request's cookies by the cookie-parser middleware
// function in app.js:
const { token } = req.cookies;
console.log("TOKEN:", token)
if (!token) {
// Send a "401 Unauthorized" response status code
return res.status(401).end();
}
return jwt.verify(token, secret, null, async (err, jwtPayload) => {
if (err) {
err.status = 401;
return next(err);
}
const { id } = jwtPayload.data;
try {
req.user = await User.findByPk(id);
} catch (e) {
// remove the token cookie
res.clearCookie("token");
return next(e);
}
if (!req.user) {
// Send a "401 Unauthorized" response status code
// along with removing the token cookie
res.clearCookie("token");
return res.status(401).end();
}
return next();
});
};
EDIT: I got it to work by changing
const { token } = req.cookies;
to:
const token = req.header('Authorization').replace('Bearer ', '')
I'm still unclear as to why the former didn't work
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);
});
});
});
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]
}),
]);
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"})
})