How to render errors from node.js in react using fetch - node.js

My application has a login page that works fine when the user enters the correct login credentials but while test casing for eventual wrong entries of either usernames or passwords I've realized that my catch block isn't able to correctly format the error object from the backend thus nothing is being rendered to the frontend.
I've tried using res.send(401).json({"message":"Unauthorized"}); in the backend instead of res.sendStatus(401); but the former method doesn't trigger an error response and rather returns as a response in the fetch.
While using res.sendStatus(401);, although the error is triggered my catch block isn't able to render it's response.
The backend:
const User = require('../model/User');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const handleLogin = async (req,res) => {
const user = req.body.user.toLowerCase();
const pwd = req.body.pwd;
if(!user || !pwd) return res.sendStatus(400);
const foundUser = await User.findOne({username: user}).exec();
if(!foundUser) return res.sendStatus(401);
const match = await bcrypt.compare(pwd, foundUser.password);
console.log(match);
if(match){
const roles = Object.values(foundUser.roles);
const accessToken = jwt.sign(
{"userInfo": {
"username": foundUser.username,
"roles": roles
}},
process.env.ACCESS_TOKEN,
{expiresIn: "300s"}
);
const refreshToken = jwt.sign(
{"username": foundUser.username},
process.env.REFRESH_TOKEN,
{expiresIn: "1d"}
);
foundUser.refreshToken = refreshToken;
const result = await foundUser.save();
if(!result) return res.status(500);
res.cookie("jwt",refreshToken,{httpOnly: true, sameSite: "None", maxAge: 24*60*60*1000});
res.json({user, roles, accessToken});
}
else{
res.sendStatus(401);
}
}
module.exports = {handleLogin};
The fetch:
fetch(BASE_URL + "/login", {
method: "POST",
headers: {
"Content-Type":"application/json"
},
body: JSON.stringify({user: username,pwd})
})
.then(res => res.json())
.then(data => {
setUser(data);
console.log(data);
})
.then(() => {
setSuccess(true);
setTimeout(() => {
navigate("/");
}, 1000);
})
.catch((err)=>{
console.log(err);
if(err.status == "401"){
setErrMsg("Wrong username or password.")
}
else{
setErrMsg("Login failed, try again.")
}
errRef.current.focus();
})
Once the error is triggered the console displays the following error SyntaxError: Unexpected token 'U', "Unauthorized" is not valid JSON and in addition to that the error is not rendered to the frontend.
How can I correctly format the response from the backend or handle the error response from the front end to be able to correctly render it to the view?

Your first then assumes that the response has valid json. Instead it should check if the response status is ok and if not, throw an error that will be caught by the catch.
.then(res => {
if (res.ok) {
return res.json();
}
throw new Error(res.status);
})

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.

Trying To Incorporate Tokens Into Sign Up

I am trying to create a token from JWT when a user signs up for my application but I get the error, Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client. I think there is an issue with the structure of my code. How can I fix this issue?
//sign up request
exports.signup = async (req, res, next)=> {
const {email} = req.body;
const userExist = await User.findOne({email});
if (userExist) {
return next(new ErrorResponse(`Email already exists`, 404))
}
try {
const user = await User.create(req.body);
res.status(201).json({
success: true,
user
})
generateToken(user, 201, res);
} catch (error) {
console.log(error);
next(error);
}
}
//generate token method
const generateToken = async (user, statusCode, res) => {
const token = await user.jwtGenerateToken();
var hour = 3600000;
const options = {
httpOnly: true,
expires: new Date(Date.now() + hour)
};
res.status(statusCode)
.cookie('token', token, options)
.json({success: true, token})
}
First generate the token and next send the response
generateToken(user, 201, res);
res.status(201).json({
success: true,
user
})
You can return value from the token and then send it using response or you can just send it from the generateToken function. Here you are trying to use the response again though you have sent it once hence resulting in error.
Updated
//sign up request
exports.signup = async (req, res, next)=> {
const {email} = req.body;
const userExist = await User.findOne({email});
if (userExist) {
return next(new ErrorResponse(`Email already exists`, 404))
}
try {
const user = await User.create(req.body);
generateToken(user, 201, res);
} catch (error) {
console.log(error);
next(error);
}
}
//generate token method
const generateToken = async (user, statusCode, res) => {
const token = await user.jwtGenerateToken();
var hour = 3600000;
const options = {
httpOnly: true,
expires: new Date(Date.now() + hour)
};
res.status(statusCode)
.cookie('token', token, options)
.json({success: true, token: token, user: user})
}
you are trying to send the response twice. and then in generateToken method. Keep it at one place, the error will go away.

I am not able to get jwt token from cookies while authorization some pages.?

I have created a MERN application in that application whenever try to login in application jwt token is generated each time whenever he/she tries to log in and stored inside the MongoDB atlas as well as browser cookies.
In the authorization part, if a user is authorized (cookies token matches with the MongoDB token) then he/she can see only the about-me page or else be redirected to the login page.
so whenever I clicked to about-me page I got err:
TypeError: Cannot read properties of undefined (reading 'jwtoken')
at Authenticate (/Users/apple/Desktop/projects/mern-auth-user/user-data-123/mern-july1/server/middleware/authenticate.js:8:35)
at Layer.handle [as handle_request] (/Users/apple/Desktop/projects/mern-auth-user/user-data-123/mern-july1/server/node_modules/express/lib/router/layer.js:95:5)
error so on....
here; How I created sign API and stored token into MongoDB and cookies. (in auth.js)
router.post("/signin", async (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ err: "invalid details" });
}
try {
const userLogin = await User.findOne({ email: email });
// console.log(userLogin);
if (!userLogin) {
res.status(400).json({ err: "invalid email" });
}
else {
const isMatch = await bcrypt.compare(password, userLogin.password);
// jwt token
const token = await userLogin.generateAuthToken();
res.cookie("jwtoken", token, {
expires : new Date(Date.now()+25892000000), // after 30 days
httpOnly: true
});
console.log(token);
if(!isMatch){
res.status(400).json({err: 'invalid pass'})
}
else{
res.status(201).json({message:'signin successfully'})
console.log(userLogin)
}
}
} catch (err) {
console.log(err);
}
});
generateAuthToken defined inside the user schema modal used inside the signin API.
userSchema.methods.generateAuthToken = async function(){
try{
const token = jwt.sign({_id : this._id}, process.env.SECRET_KEY); // _id(asking unique id) : this._id (taken id from database of corresponding login email)
this.tokens = this.tokens.concat({token : token}) // storing jwt token into tokens. token(from userSchema) : token (value from generated token)
await this.save();
return token;
}
catch(err){
console.log(err);
}
}
this is my middleware "Authenticate"
const jwt = require('jsonwebtoken');
const User = require('../models/userSchema');
const Authenticate = async (req, res, next) => {
try {
//get jwt token from cookies
const token = req.cookies.jwtoken;
//verifying token with SECRET_KEY
const verifyToken = jwt.verify(token, process.env.SECRET_KEY);
// get user data from token, if token id(from cookies)===tokens.token
const rootUser = await User.findOne({ _id: verifyToken._id, "tokens.token": token });
if (!rootUser) { throw new Error('user not found') }
// if get user
req.token = token;
// get user's all data in rootUser
req.rootUser = rootUser;
// get id od rootUser
req.userID = rootUser._id;
next();
}
catch (err) {
res.status(401).send("unauthorized: no token provided")
console.log(err)
}
}
module.exports = Authenticate;
used inside the about-me API; auth.js
router.get('/about', authenticate, (req, res) => {
// console.log(Authenticate.token)
res.send('hello world from server side');
res.send(req.rootUser)
})
now code inside the Reactjs
About.js
const history = useHistory();
const callAboutPage = async () =>{
try{
const res = await fetch('/about', { //this res is backend response , not from call back function
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
credentials: "include"
});
const data = await res.json();
console.log(data)
if(! res.status === 200){
const error = new Error(res.error);
throw error;
}
}
catch(err){
console.log(err)
history.push('/signin');
}
};
useEffect(() => {
callAboutPage()
}, [])
These is my cookies stored in the browser
enter image description here
please help me to get the about-me page right now I am not able to see my page even I have cookies inside my application(browser).
Cannot read properties of undefined (reading 'jwtoken') indicates that req.cookies is undefined in Authenticate.js.
You may miss the middleware cookie-parser for express, check express doc (req.cookies) here.
To fix this, follow the example of cookie-parser (npm) would help. Code like below will allow you to read cookies from req.cookies.
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());

My cookie does not set in my browser, How can I fixed?

I've working with jwt and cookies I don't why my cookie does not set in my browser.
If I do this in postman works properly, create cookie and looks well, but when I do this in my browser with my react form, cookie does not set.
Please any advice
This my frontend code
const onSubmit = useCallback(async (e) => {
e.preventDefault()
setStatus('pending')
setError(null)
const credentials = {
email: values.email,
password: values.password
}
console.log(credentials)
try {
const res = await API.post(LOGIN_API, credentials)
console.log(res.data)
if (res.data.success) {
setStatus('success')
navigate('/')
} else {
setStatus('error')
}
} catch (err) {
setError(error)
}
}, [values, error, navigate])
my backend
const login = async (req, res) => {
const text = 'SELECT user_id, password, email FROM user_account WHERE email=$1;'
const values = [req.body.email]
try {
const response = await client.query(text, values)
const match = await bcrypt.checkPassword(req.body.password, response.rows[0].password)
if (match) {
res.cookie("access_token", jwt.sign({ id: response.rows[0].user_id }, jwtSecret), { httpOnly: true })
res.status(200).json({ success: true, message: "Logged in successfully" })
} else {
res.status(401).json({ success: false, message: "Credentials are not valid" })
}
} catch (err) {
console.log(err.stack)
}
}
And then here my axios instance
import axios from 'axios'
export default axios.create({
baseURL: process.env.REACT_APP_BASE_API_URL
})
Cookies can only be set if you load a resource by a browser, no through JavaScript. There is no way to set httponly cookie this way.
Usually jwt tokens are stored in localStorage

Resources