JWT expired, unexpected response in nodejs API - node.js

i have a problem when i try to use a private api in my node.js server, This is the process that i am following
SignUp (if the user doesn't have an account) or logIn (already have an account).
It generates the token and i pass it in the header res.header('access_token': token)
I copy this token and paste it in my private api in the header section (i'm using postman to test it for now) in order to verify if the user is logged in.
In my route, i use a middleware to turn it private, validating if the user can use the resource jwt.verify(authorization[1], process.env.SEED_AUTH) (SEED_AUTH is my token secret stored in my server)
Here is when i have the error, the middleware is failling to verify the user and throw me this error jwt expired
This is my route
const UsersController = require('../controllers/users.controller')
const AuthorizationMiddleware = require('../middlewares/authorization.middleware')
exports.routesConfig = (app) => {
app.post('/api/users',[
AuthorizationMiddleware.verifyValidJWT
], UsersController.insert)
}
This is my middleware
const jwt = require('jsonwebtoken')
require('../config/env.config')
exports.verifyValidJWT = (req, res, next) => {
if (req.headers['access-token']) {
try {
let authorization = req.headers['access-token'].split(' ');
if (authorization[0] !== 'Bearer') {
return res.status(401).json({
ok: false,
err: "Unauthorized, Need a valid token"
});
} else {
console.log(authorization[1]);
req.jwt = jwt.verify(authorization[1], process.env.SEED_AUTH);
return next();
}
} catch (error) {
return res.status(403).json({
ok: false,
err: "Forbidden, need a valid token -> " + error.message
});
}
} else {
return res.status(401).json({
ok: false,
err: "Need to recieve a valid token"
});
}
}
And finally the API UsersController.insert
What i'm trying to do with this api is to create a new user.
For a better understanding this is my LOGIN API
const User = require('../models/users.model')
const { signUpValidation, logInValidation } = require('../middlewares/auth.validation.data')
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')
exports.logIn = async (req, res) => {
let body = req.body;
//Validation
const { error } = logInValidation(body)
if (error) {
return res.status(400).json({
ok: false,
err: error.details[0].message
})
}
//Check if the email already exists in the database
const user = await User.findOne({ email: body.email })
if (!user) {
return res.status(400).json({
ok: false,
err: "Invalid Email or password!"
})
}
const validPass = await bcrypt.compareSync(body.password, user.password)
if (!validPass) {
return res.status(400).json({
ok: false,
err: "Invalid Email or password!"
})
}
const token = jwt.sign(
{
_id: user._id,
email: user.email
},
process.env.SEED_AUTH,
{
expiresIn: process.env.TOKEN_EXPIRY
}
)
res.header('access-token', token).json({
ok: true,
user: {
id: user._id,
email: user.email
}
});
}
SignUp and LogIn validation
I use these middleware to verify if it is a valid email, and the name with a minimum number of letters...
My process.env.TOKEN_EXPIRY is set to 300 (i understand that, it is in seconds), i've tried with bigger number though
(The API works without the middleware).
What would be the problem that i am not seeing. Thanks for your help.

process.env variables are as string and not as a number. According to the jsonwebtoken documentation, string is considered as milliseconds by default and number is counted as seconds by default. So change TOKEN_EXPIRY to 300000 from 300

Related

Not Able to Login Without giving auth token in the header

This is a todo list web app, I have used nodejs and reactjs in it
I am not able to use the login feature , It shows me the error : invalid token
I have tried hard coding the token (which generates on the sign up) and that way it worked. But with the below code it doesnt work.
Using JWT for Authentication token generation
Funtion that handles the Login Click (user puts email and password)
const handleSubmit = async (e) => {
e.preventDefault();
const response = await fetch('http://localhost:5000/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email: credentials.email, password: credentials.password })
});
const json = await response.json();
if (json.success) {
localStorage.setItem('token', JSON.stringify(json.authToken));
showAlert('Successfully Logged in');
navigate("/");
} else {
alert("Invalid credentials");
}
}
Backend Api Call (Using Nodejs and Express)
router.post("/login", fetchUser,
[
body("email", "Enter a valid email").isEmail(),
body("password", "Password cannot be blank").exists(),
], async (req, res) => {
let success = false;
// if there are errors, handle them with bad requests
const errors = validationResult(req);
if (errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const { email, password } = req.body;
// Check if the user with requested email exists in the database
let user = await User.findOne({ email });
if (!user) {
success = false;
return res.status(400).json({ success, error: "Please enter the correct credentials" });
}
// Check if the user with requested passwork exists in the database
const comparePassword = await bcrypt.compare(password, user.password);
if (!comparePassword) {
success = false;
return res.status(400).json({ success, error: "Please enter the correct credentials" });
}
// Auth Token Generation using jwtToken
const data = {
user: {
id: user.id,
},
};
success = true;
let authToken = jwt.sign(data, JWT_Secret);
res.json({ success, authToken });
} catch (error) {
res.status(500).send("Internal error occured");
}
});
When I tried Hardcording the auth-token it worked
By clicking on login the new auth-token should be generated and set as 'token' in the local storage. Through which data will be accessed using different end points.
At this line json.authToken is a string already. You don't need to stringify it again.
localStorage.setItem('token', JSON.stringify(json.authToken))
Just remove the function and it'll be fine.
localStorage.setItem('token', json.authToken)

Trying access the jws token in middleware file. jws token alwasy undefine in middleware file

I'm having trouble accessing my jwt token in the middleware file. I'm getting a token. But when I decoded it using online (https://jwt.io/) it says Invalid Signature. But the decoded result shows the correct email and hash password. Here is the userController.js file. I'm using node.js and express.js for this project
export const signUp = async (req, res) => {
const { email, password, fName, lName, confirmPassword } = req.body;
try {
const existingUser = await userModel.findOne({ email });
if (existingUser)
return res.status(400).json({ message: "User already exist" });
if (password !== confirmPassword)
return res.status(400).json({ message: "Password don't match" });
const hashedPasswoed = await bcrypt.hash(password, 12);
const result = await userModel.create({
email,
password: hashedPasswoed,
name: `${fName} ${lName}`,
});
const token = jwt.sign({ email: result.email, id: result._id }, "test", {
expiresIn: "1h",
}); // secret = test
res.status(200).json({ result, token });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
then I'm trying to access the above token in my middleware.js file.
import jwt from "jsonwebtoken";
const authMiddleware = async (req, res, next) => {
try {
const res = req.headers.authorization; // always undefine
// const res = //tryed this way also but undefine
// req.body.token || req.query.token || req.headers["authorization"];
const token = res.split(" ")[1];
const isCustomeAuth = token.lenght < 500;
let decodedData;
if (token && isCustomeAuth) {
decodedData = jwt.verify(token, "test");
req.userId = decodedData.id;
} else {
decodedData = jwt.decode(token);
req.userId = decodedData.sub;
}
next();
} catch (error) {
console.log(error);
}
};
export default authMiddleware;
'res' always undefine. Please help me to access the token in middleware as I'm trying this for days and got nowhere
In the client-side API file, I added an API interceptor. Then it got fixed
API.interceptors.request.use((req) => {
if (localStorage.getItem("profile")) {
req.headers.Authorization = `Bearer ${
JSON.parse(localStorage.getItem("profile")).token
}`;
}
}
Now I can access the req.header.authorization and use the split function.

I am trying to save the reset password back to database but request taking longer until i cancel

I am not receiving any errors but when I test my endpoint it's taking longer and no response until I terminate the request. I am sending a user an email with a token that will be used to validate if he exists in the database so that he can change his/her password but I have not succeeded for the last two days. I am frustrated now, I have never done this before.
The Middleware that sends the reset password link
export class sendGridEmail {
static async sendResetPasswordEmail(email, token) {
sendGrid.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: `${email}`,
from: `${process.env.VERIFIED_SENDER}`, // Change to your verified sender
subject: "RESET YOUR PASSWORD",
text: `You are receiving this email because you (or someone else) has requested the reset of a password. Follow this link ${process.env.BASE_URL}/api/resetpassword/${token}`,
};
return sendGrid
.send(msg)
.then(() => {
console.log(`password rest link has been sent to: ${email}`);
})
.catch((err) => {
console.log(err);
});
}
}
The Component that sends the reset password link
export const sendResetPasswordLink = asynchandler(async (req, res) => {
const { email } = req.body;
const user = await userModel.findOne({ email });
if (!user) {
res.status(404);
res.json({ message: "account with this email was not found" });
} else if (user) {
const token = AuthToken(user._id);
try {
await sendGridEmail.sendResetPasswordEmail(user.email, token);
res.status(200);
res.json({
message: `password reset link hase been sent to: ${user.email}`,
});
} catch (error) {
res.status(500);
res.json({ message: error });
}
} else {
res.status(500);
res.json({ message: "Internal Server Error" });
}
});
The Route that tries to save the password. Am I getting it wrong by verifying the token in the params using jwt and then checking if the user exists or am I missing out something ?
export const resetPassword = asynchandler(async (req, res) => {
const { resetToken } = req.params;
const private_key = process.env.PRIVATE_KEY;
const payload = jwt.verify(resetToken, private_key);
const user = await userModel.findById(payload.id);
console.log(payload.id);
if (!user) {
res.status(404);
res.jsonp({ message: "token has expired" });
}else if(user){
user.password= req.body.password
await user.save();
await resetToken.delete();
await sendMessage.sendPasswordResetSuccess(user.number);
res.status(200);
res.json({message:"password changed succesfully"});
}else{
res.status(500)
res.json({message:"no token was procide"})
}
});
The routes
app.post('/api/resetlink', sendResetPasswordLink);
app.put("/api/resetpassword/:resetToken", resetPassword);

How to get current user in another file while working on postman and node.js

I have this code for login. How do I use the current user's information from this code to another file using postman and node.js?
exports.loginUser = (req,res, next) => {
User.find({email: req.body.email})
.exec()
.then(user => {
if(user.length < 1) {
return res.status(401).json({
message: 'Auth failed'
});
}
bcrypt.compare(req.body.password, user[0].password, (err ,result) => {
if(err){
return res.status(401).json({
message: 'Auth failed'
});
}
if (result) {
const token = jwt.sign({
email: user[0].email,
userId: user[0]._id
},
process.env.JWT_KEY ,
{
//options
expiresIn: "1h"
});
You should tell exactly what you want, what you said is confusing, but If you mean how to pass the logged in user to the next middleware, you gotto assign the user to req
exports.loginUser = async (req, res, next) => {
const user = await User.find({ email: req.body.email }).exec()
if (user.length < 1) {
return res.status(401).json({
message: 'Auth failed'
});
}
bcrypt.compare(req.body.password, user[0].password, (err, result) => {
if (err) {
return res.status(401).json({
message: 'Auth failed'
});
}
if (result) {
const token = jwt.sign({
email: user[0].email,
userId: user[0]._id
},
process.env.JWT_KEY, {
//options
expiresIn: "1h"
});
req.user = user[0];
return next();
}
})
}
Then in the next middleware you have access to logged in user, using req.user.
UPDATE:
To implement the functionality that you want, according to what you described in the comment:
Before anything import these packages:
const jwt = require("jsonwebtoken");
const { promisify } = require("util");
First you implement a route that checks for credentials and sends back a signed jwt:
exports.login = CatchAsync(async(req, res, next) => {
const { email, password } = req.body;
if (!email || !password) {
return next(new Error("Please provide email and password"));
}
const user = await UserModel.findOne({email});
if (!user) {
return next(new Error("There is no user with that email"));
}
if(!(await bcrypt.compare(password, user.password))) {
// actually the pass is not correct but for security reasons we don't say that
return next(new Error("Email or password is not correct");
}
// pass the user id to jwt so later can identify user
const token = jwt.sign({ id: user._id }, 'yourJwtSecret', {
expiresIn: '90d',
});
// httpOnly prevents access to token in client's browser, so it is safe
const cookieOptions = {
expires: new Date(
Date.now() + 90 * 24 * 60 * 60 * 1000
),
httpOnly: true,
};
res.cookie("jwt", token, cookieOptions);
res.status(200).json({
status: 'success',
message: 'logged in successfully'
});
});
Then for every route that needs to check for logged In user, use this middleware:
exports.isLoggedIn = CatchAsync(async(req, res, next) => {
// Check if there is a token
// if no token was provided it means user is not logged in
let token;
if (req.cookies.jwt) {
token = req.cookies.jwt;
} else {
return next();
}
// Verify token
// decoded now has access to id of user
let decoded;
try {
decoded = await promisify(jwt.verify)(token, 'yourJwtSecret');
} catch (err) {
// if token was modified or expired or not valid
return next();
}
// get the user
const user = await UserModel.findOne({
_id: decoded.id
});
// access granted, user is logged in
req.user = user; // you can access the logged in user in the next middleware
res.locals.user = user; // you can access the logged in user in template engines
next();
});
If the user is not logged in, req.user won't be assigned. therefore in next middlewares if req.user was undefined you know user is not logged in.
for more info jwt docs.
If you have never taken any NodeJs course, I'd recommend this course

Getting req.user.following undefined

I'm creating social network app with MERN. I have implemented followers and following, and now i'm trying to get the list of posts of only users that i'm following. So, if I console.log my req.user I only get the 'id' and 'iat', but i need more information of a user such as following array.
here is what i have:
auth.js middlware:
const config = require('config')
const jwt = require('jsonwebtoken')
function auth(req, res, next) {
const token = req.header('x-auth-token')
// check for token
if (!token) return res.status(401).json({ msg: 'Unauthorized token' })
try {
// verify token
const decoded = jwt.verify(token, config.get('jwtSecret'))
// add user from payload
req.user = decoded
console.log('---',decoded)
next()
} catch (e) {
return res.status(400).json({ msg: 'Token is not valid' })
}
}
module.exports = auth
auth.js
const express = require('express')
const router = express.Router()
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const config = require('config')
const User = require('../models/User')
const auth = require('../middleware/auth')
// #route POST /api/auth
// #desc Login user
// #access Public
router.post('/', (req, res) => {
const { email, password } = req.body
// filed validation
if (!email || !password) res.status(400).json({ msg: 'Fields cannot be empty.' })
// Check if user is registered
User
.findOne({ email })
.then(user => {
if (!user) res.status(400).json({ msg: `User doesn't exist.` })
//console.log(user)
// validate password
bcrypt
.compare(password, user.password)
.then(isMatch => {
if (!isMatch) return res.status(400).json({ msg: 'Invalid password' })
// if password matches, send token and user
jwt.sign(
{ id: user.id },
config.get('jwtSecret'),
(err, token) => {
if (err) throw err
res.json({
token,
user: {
id: user.id,
email: user.email,
first_name: user.first_name,
last_name: user.last_name,
registration_date: user.registration_date,
profile_image: user.profile_image,
user_bio: user. user_bio,
followers: user.followers,
following: user.following
}
})
}
)
})
})
})
and this is my console log
Server started at port 5000
[0] --- { id: '5efccfb13224e1489439bcfd', iat: 1593626545 }
[0] * { id: '5efccfb13224e1489439bcfd', iat: 1593626545 }
EDIT:
// #route GET /api/posts/subscribedPost
// #desc get all subscribed post
// #access Private
router.get('/subscribedPost', auth, (req, res) => {
console.log('*',req.user) // not working correctly, have only id and iat
Post
.find({userID: req.user.id})
.populate('userID', 'first_name last_name profile_image _id')
.sort({ registration_date: -1 })
.then(post => res.json(post))
.catch(err => res.json(err))
})
This is because you are signing your JWT token with id only, so when u decode it you will only get id
jwt.sign({ id: user.id },config.get('jwtSecret'),(err, token) =>{})
Here you are just passing id, you can pass your whole user object to get all the data
jwt.sign(user.toJSON(),config.get('jwtSecret'),(err, token) =>{})
PS- YOU MIGHT NOT WANT TO PASS YOUR WHOLE USER, JUST DID IT FOR DEMO
You can customize to what values you want.
You need to get user data DB in auth middleware and then add it to req.user. So if there are any changes to the user you get updated user data.
eg:
function auth(req, res, next) {
const token = req.header('x-auth-token')
// check for token
if (!token) return res.status(401).json({ msg: 'Unauthorized token' })
try {
// verify token
const decoded = jwt.verify(token, config.get('jwtSecret'))
// get the user from DB
const user = User.findById(decoded.id) //only populate fields which are required
req.user = user
console.log('---',decoded)
next()
} catch (e) {
return res.status(400).json({ msg: 'Token is not valid' })
}
}

Resources