JsonWebTokenError: jwt must be a string, node.js - javascript

I'm getting the error JsonWebTokenError: jwt must be a string when getting the jwt from the front end (react.js) and using in middleware to verify the token. If I tried to use toString it gives me another error JsonWebTokenError: jwt malformed.
Update
As soon as i pass the accessToken from frontEnd it converted into object in the AuthMiddleware.js. I'm passing middleware on header in file Post.js(attached below)
AuthMiddleware.js
const { verify } = require("jsonwebtoken")
const validateToken = (res, req, next) => {
const accesToken = req.header("accesToken");
const stringAccesToken = accesToken.toString()
console.log(typeof (stringAccesToken), "accesToken type")
if (!stringAccesToken) {
return res.json({ error: "user not logged in" })
}
try {
const validToken = verify(stringAccesToken, "importantSecret");
console.log(validToken, 'validtoken')
if (validToken) {
return next();
}
} catch (err) {
console.log(err, "error")
}
}
module.exports = { validateToken }
User.js (backend for login)
const express = require("express");
const router = express.Router()
const { Users } = require("../models")
const bcrypt = require("bcrypt")
const { sign } = require("jsonwebtoken")
router.post("/login", async (req, res) => {
const { username, password } = req.body;
const user = await Users.findOne({ where: { username: username } });
if (!user) {
return res.json({ error: "User dosen't exist" })
}
bcrypt.compare(password, user.password)
.then((match) => {
if (!match) {
return res.json({ error: "Wrong Username and Password match" })
}
const accessToken = sign({ username: user.username, id: user.id }, "importantSecret")
res.json(accessToken)
})
})
module.exports = router;
Post.js
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom';
import axios from 'axios';
import './Post.css'
function Post() {
let { id } = useParams();
const [postObject, setPostObject] = useState({})
const [comments, setComments] = useState([]);
const [newComment, setNewComment] = useState("");
// console.log(comments)
const addComment = () => {
const accessToken = sessionStorage.getItem('accessToken')
console.log(typeof (accessToken), 'acces token in comment button')
axios.post(`http://localhost:4000/comments`,
{
commentBody: newComment,
PostId: id
},
{
headers: {
accessToken: sessionStorage.getItem("accessToken"),
}
}
)
.then((res) => {
// console.log(res)
const data = res.data;
console.log(data, 'comments')
setComments([...comments, data])
setNewComment("")
})
.catch((err) => {
alert(err, 'Error:comment')
})
}
useEffect(() => {
axios.get(`http://localhost:4000/posts/byId/${id}`)
.then((res) => {
// console.log(res)
const data = res.data;
// console.log(data)
setPostObject(data)
// setPost(data)
})
// comment api request
axios.get(`http://localhost:4000/comments/${id}`)
.then((res) => {
// console.log(res)
const data = res.data;
// console.log(data)
setComments(data)
})
}, [])
return (
<div className='Post'>
<div className='left__side'>
<div className='left__side__wrapper'>
<div className='title'>{postObject.title}</div>
<div className='text'>{postObject.postText}</div>
<div className='username'>{postObject.username}</div>
</div>
</div>
<div className='right__side'>
<div className='right__side__wrapper'>
<div className='add__comment__container'>
<input type="text"
value={newComment}
placeholder="Comment"
// autoComplete="off"
onChange={(e) => setNewComment(e.target.value)}
/>
<button onClick={addComment}> Submit Comment</button>
</div>
<div className='listOfCommnets'>
{comments.map((item, index) => {
{/* console.log(item, 'item') */ }
return <div className='comments' key={index}>Comments:<br />{item.commentBody}</div>
})}
</div>
</div>
</div>
</div>
)
}
export default Post

Because the jwt token is using an object as an input rather than using the word "verify," it won't work with the object in which you are receiving the error any longer. Instead, you must attempt as follows.
var jwt = require("jsonwebtoken");
const validateToken = (res, req, next) => {
const accesToken = req.header("accesToken");
const stringAccesToken = accesToken;
if (!stringAccesToken) {
return res.json({ error: "user not logged in" });
}
jwt.verify(stringAccesToken, "importantSecret", function (err, decoded) {
if (err)
return console.log(err)
// Next Code
next();
});
};
module.exports = { validateToken };

I found the solution:
As the error said JWT need to be string,
I first tried using accesToken.toString() that gives [object object],
On second try i used JSON.stringy and that was also unsuccessful.
Final scuccesful attemp was to use library name - flatted (to convert json to string and after using it i just used split till i did'nt get the token).
faltted (link) - https://github.com/WebReflection/flatted#flatted
Worked Solution -
AuthMiddleware.js
const { parse, stringify, toJSON, fromJSON } = require('flatted');
const validateToken = (res, req, next) => {
const authToken = req.header("accessToken");
const string = stringify(authToken)
const token = string && string.split(' ')[2];
const tokenPure = token.split('"')[4]
if (!tokenPure) {
return res.json({ error: "user not logged in" })
}
try {
const validToken = verify(tokenPure, "importantSecret");
// console.log(validToken, 'validtoken')
if (validToken) {
return next();
}
} catch (err) {
console.log(err, "error")
}
}
module.exports = { validateToken }

Related

When I use fetch my .then code isn't working

So I am trying to redirect after I am deleting the page, it get's deleted from the database but it doesn't redirect me to my homepage. It worked fine when I was using json-server locally, but when I started using Mongoose it wasn't working properly and wasn't redirecting.
The code inside .then isn't working, I tried console.log inside the .then but it didn't log
I am using mongoose as my database
Here is my entire component:
import { useParams } from "react-router-dom";
import useFetch from "../useFetch";
import { useHistory } from "react-router-dom";
import moment from "moment";
import profile_image from "../images/picture.jpg";
const BlogDetails = () => {
let blogDate = moment().format('D MMM YYYY');
const { id } = useParams();
const { data: blog, isPending, errorMsg } = useFetch("http://localhost:5000/postsdata/" + id);
const history = useHistory()
const handleDelete = () => {
fetch('http://localhost:5000/postsdata/' + blog._id, { method: 'DELETE' })
.then(() => {
history.push('/');
})
.catch((err) => console.log(err))
}
return (
<div className="blog-details">
<div className="top-profile">
<div className="top-profile-picture">
<img src={profile_image} alt="profile-pic-top" />
</div>
<div className="top-profile-name">
<p>Vishwajeet Deshmukh</p>
</div>
</div>
{isPending && <div>Loading...</div>}
{errorMsg && <div>{errorMsg}</div>}
{blog && (
<article className="blog-main-content" >
<div className="main-content-header">
<div className="content-title-date">
<h2 className="blogdetails-title">{blog.title}</h2>
<p className="blogdetails-date">{blogDate}</p>
</div>
<div className="content-image">
<img src={blog.imgsrc} alt="" />
</div>
</div>
<div className="blogdetails-body"><p>{`${blog.postBody}`}</p></div>
<button className="blogdetails-delete" onClick={handleDelete}>Delete Me</button>
</article>
)}
</div>
);
};
export default BlogDetails;
Here is my router.js which handles my delete
const express = require('express');
const router = express.Router();
const { Posts } = require("./models");
//<----------------------------------- CRUD OPERATIONS ------------------------------------------>
router.get("/", () => {
console.log("Server Connected");
})
//<---------------------------- Get Posts from Database ---------------------------->
router.get("/postsdata", (req, res) => {
Posts.find((err, data) => {
if (err) {
res.status(500).send(err);
} else {
res.status(201).send(data);
}
return null;
})
})
//<------------- Get Specific Posts from Database --------------->
router.get("/postsdata/:_id", (req, res) => {
const id = req.params._id;
Posts.findById(id, (err, data) => {
if (err) {
res.status(500).send(err);
throw new Error(err)
} else {
res.status(201).send(data);
}
return data;
})
})
//<---------------------------- Post On the Posts Database ---------------------------->
router.post("/postsdata", (req, res) => {
const db = req.body;
Posts.create(db, err => {
if (!err) {
console.log("Posted on Server");
} else {
throw new Error(err)
}
return null
})
})
//<---------------------------- Delete Posts from Database ---------------------------->
router.delete("/postsdata/:id", (req, res) => {
const id = req.params._id
Posts.deleteOne(id, (err, data) => {
if (err) {
console.log(err);
throw new Error(err)
} else {
console.log(data);
}
return null
})
})
module.exports = router;
after deleting the postdata, send a response from the API.
router.delete("/postsdata/:id", (req, res) => {
const id = req.params._id
Posts.deleteOne(id, (err, data) => {
if (err) {
console.log(err);
throw new Error(err)
} else {
return res.status(200).json({status: 'success'}); // try with this
}
return null
})
})
Hello try it with async/await sayntax
const handleDelete = async () => {
await fetch('http://localhost:5000/postsdata/' + blog._id, { method: 'DELETE' });
history.push('/');
}

Authorizing the user

I am having a slight problem with authorizing the admin.
The backend code works, but i have got problems with requesting the admin route and authenticating the logged in user. First thing i have tried was to put the isAdmin value in the cookies, but it wasnt secure. Then i tried to verify the admin with cookies, i used cookie.get() to get the token. But it was not a success.
code Authorization:
const isAdmin = async (req, res, next) => {
if (!req.user.isAdmin) {
res.status(401).send({ msg: "Not an authorized admin" });
} else {
res.send(req.user.isAdmin);
// const token = req.header("auth-token");
// const verified = verify(token, process.env.SECRET);
// req.user = verified;
// next();
}
next();
};
code Admin route:
router.get("/adminPanel", isAuth, isAdmin, (req, res) => {});
code Login page:
const handleSubmit = e => {
e.preventDefault();
Axios.post("http://localhost:5000/users/login", {
email,
password,
})
.then(response => {
cookie.set("token", response.data.token, {
expires: 1,
});
setUser({
token: response.data.token,
});
if (response.data.isAdmin) {
alert("admin");
} else {
alert("not an admin");
}
// console.log(response.data.token);
// console.log(response.data.isAdmin);
})
.catch(err => {
console.log(err);
});
};
code Admin page:
import React, { useContext, useEffect, useState } from "react";
import Axios from "axios";
import { userContext } from "../../App";
export default function Home() {
const [user, setUser] = useContext(userContext);
const [content, setContent] = useState("login plz to display the content");
useEffect(() => {
// Axios.get("http://localhost:5000/users/adminPanel").then(response =>
// console.log(response.data),
// );
// async function fetchAdmin() {const result = await
Axios.get("http://localhost:5000/users/adminPanel", {
headers: {
Authorization: `Bearer ${user.isAdmin}`,
},
});
// }
// fetchAdmin();
// async function fetchProtected() {
// const result = await (
// await fetch("http://localhost:5000/users/adminPanel", {
// method: "GET",
// headers: {
// "Content-Type": "application/json",
// authorization: `Bearer ${user.token}`,
// },
// })
// ).json();
// if (result.isAdmin) setContent("Admin");
// }
// fetchProtected();
}, [user]);
return `${content}`;
}
Getting the token from cookies:
const [user, setUser] = useState({});
useEffect(() => {
setUser({ token: cookie.get("token") });
}, []);
console.log(user);
Taking into account your route
router.get("/adminPanel", isAuth, isAdmin, (req, res) => {});
I assume req.user.isAdmin is set in isAuth middleware, so your isAdmin middleware should check that parameter, let it pass if so, or reject it otherwise.
In the isAuth middleware after you validate the user, you should know if is an admin or not, so just set the parameter like this:
const isAuth = async (req, res, next) => {
// other code
req.user.isAdmin = true // put your logic here to reflect the status
next(); // pass the control to next middleware, in your example to isAdmin
}
Finally isAdmin could look like this:
const isAdmin = async (req, res, next) => {
if (!req.user.isAdmin) {
res.status(401).send({ msg: "Not an authorized admin" });
} else {
next();
}
};

redux not catching errors

So whenever the app is loaded it should check for user Auth using the loadUser(), the problem I'm having is that if there is no token in localStorage, the server won't return any errors(when its suppose to). I looked at the code for auth(backend), and it returns a status meassage when no token received, I was wondering is it because no token isn't a type of error, that's way the server didn't send an error response?
Below are the code snippets:
auth.js(backend)
const jwt = require("jsonwebtoken");
const config = require("config");
module.exports = function (req, res, next) {
//get token from header
const token = req.header("x-auth-token");
// check if not token
if (!token) {
return res.status(401).json({ msg: "no token, auth denied" });
}
//verify token
try {
const decoded = jwt.verify(token, config.get("jwtSecret"));
req.user = decoded.user;
next();
} catch (err) {
res.status(401).json({
msg: "token isnt valid",
});
}
};
App.js
const App = () => {
useEffect(() => {
if (localStorage.token) {
setAuthToken(localStorage.token);
store.dispatch(loadUser());
}
}, []);
auth.js Redux
export const loadUser = () => async (dispatch) => {
console.log("from auth.js");
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get("/api/auth");
console.log("inside auth.js get auth route");
dispatch({
type: USER_LOADED,
payload: res.data,
});
} catch (err) {
console.log("error from auth.js");
dispatch({
type: AUTH_ERROR,
});
}
};
Basically the code inside catch(err) { //code }
is not executed.
Silly of me, added else condition into App.js solved the issue.

Error "Assignment to constant variable" in ReactJS

I did follow a tutorial of how to integrate mailchimp with node backend. I have never touched back end, so am pretty lame at it.
When I POST to their API I get the subscriber's credentials, but I get an error back - "Assignment to constant variable". Reading through the web and other SO questions, it seems like I am trying to reassign a CONST value.
I had a goooooooooood look at my code and the only thing I have noticed that might be issues here is
request(options, (error, response, body) => {
try {
const resObj = {};
if (response.statusCode == 200) {
resObj = {
success: `Subscibed using ${email}`,
message: JSON.parse(response.body),
};
} else {
resObj = {
error: ` Error trying to subscribe ${email}. Please, try again`,
message: JSON.parse(response.body),
};
}
res.send(respObj);
} catch (err) {
const respErrorObj = {
error: " There was an error with your request",
message: err.message,
};
res.send(respErrorObj);
}
});
I have noticed I am creating an empty object called "resObj", then trying to assign a value to it.
I have tried changing the CONST to LET, but I get an error saying: "resObj is not defined".
Here is my front end code:
import React, { useState } from "react";
import "./App.css";
import Subscribe from "./components/Subscribe";
import Loading from "./components/Loading/Loading";
import axios from "axios";
import apiUrl from "./helpers/apiUrl";
function App() {
const [loading, setLoading] = useState(false);
const [email, setEmail] = useState("");
const handleSendEmail = (e) => {
setLoading(true);
console.log(email);
axios
.post(`${apiUrl}/subscribe`, { email: email })
.then((res) => {
if (res.data.success) {
alert(`You have successfully subscribed!, ${res.data.success}`);
setEmail("");
setLoading(false);
} else {
alert(`Unable to subscribe, ${res.data.error}`);
console.log(res);
setLoading(false);
setEmail("");
}
})
.catch((err) => {
setLoading(false);
alert("Oops, something went wrong...");
console.log(err);
setEmail("");
});
e.preventDefault();
};
const handleInput = (event) => {
setEmail(event.target.value);
};
// const handleLoadingState = (isLoading) => {
// setLoading({ isLoading: loading });
// console.log(loading);
// };
return (
<div className='App'>
<h1>Subscribe for offers and discounts</h1>
{loading ? (
<Loading message='Working on it...' />
) : (
<Subscribe
buttonText='Subscribe'
value={email}
handleOnChange={handleInput}
handleOnSubmit={handleSendEmail}
/>
)}
</div>
);
}
export default App;
And the Back end code:
const restify = require("restify");
const server = restify.createServer();
const corsMiddleware = require("restify-cors-middleware");
const request = require("request");
require("dotenv").config({ path: __dirname + "/variables.env" });
const subscribe = (req, res, next) => {
const email = req.body.email;
const dataCenter = process.env.DATA_CENTER;
const apiKey = process.env.MAILCHIMP_API_KEY;
const listID = process.env.LIST_ID;
const options = {
url: `https://${dataCenter}.api.mailchimp.com/3.0/lists/${listID}/members`,
method: "POST",
headers: {
"content-type": "application/json",
Authorization: `apikey ${apiKey}`,
},
body: JSON.stringify({ email_address: email, status: "subscribed" }),
};
request(options, (error, response, body) => {
try {
const resObj = {};
if (response.statusCode == 200) {
resObj = {
success: `Subscibed using ${email}`,
message: JSON.parse(response.body),
};
} else {
resObj = {
error: ` Error trying to subscribe ${email}. Please, try again`,
message: JSON.parse(response.body),
};
}
res.send(respObj);
} catch (err) {
const respErrorObj = {
error: " There was an error with your request",
message: err.message,
};
res.send(respErrorObj);
}
});
next();
};
const cors = corsMiddleware({
origins: ["http://localhost:3001"],
});
server.pre(cors.preflight);
server.use(restify.plugins.bodyParser());
server.use(cors.actual);
server.post("/subscribe", subscribe);
server.listen(8080, () => {
console.log("%s listening at %s", server.name, server.url);
});
If anyone could help I would be very grateful. The subscription form works, but I need to clear that bug in order for my front end to work correctly onto submission of the form.
Maybe what you are looking for is Object.assign(resObj, { whatyouwant: value} )
This way you do not reassign resObj reference (which cannot be reassigned since resObj is const), but just change its properties.
Reference at MDN website
Edit: moreover, instead of res.send(respObj) you should write res.send(resObj), it's just a typo

Getting error " Route.post() requires a callback function but got a [object Undefined]"

Can anyone explain to me why I'm getting this error? Here's my code where I'm getting this error. I assume it's becuase of the imports/exports in my code?
emailController
const User = require("../models/User")
const jwt = require("jsonwebtoken")
const { transporter, getResetPasswordURL, resetPasswordTemplate } = require("../utils/mailer")
module.exports = {
createOneTimeTokenAndSendMail: async (req, res) => {
const email = req.params.email
try {
const user = await User.findOne({ email })
if (!user) {
return res.status(404).json({ error: "No user with that email "})
}
const hashedPassword = user.password
const createdAt = user.createdAt
const userId = user._id
const secret = hashedPassword + "-" + createdAt
const token = jwt.sign({ userId }, secret, {
expiresIn: 3600
})
const url = getResetPasswordURL(user, token)
const emailTemplate = resetPasswordTemplate(user, url)
const sendEmail = () => {
transporter.sendMail(emailTemplate, (err, info) => {
if (err) {
res.status(500).json("Error sending email")
}
console.log("email sent", info.response)
})
}
sendEmail()
} catch (error) {
res.status(500).json({ error })
}
}
}
mailer
const User = require("../models/User")
const jwt = require("jsonwebtoken")
const {
transporter,
getResetPasswordURL,
resetPasswordTemplate
} = require("../utils/mailer")
module.exports = {
createOneTimeTokenAndSendMail: async (req, res) => {
const email = req.params.email
try {
const user = await User.findOne({ email })
if (!user) {
return res.status(404).json({ error: "No user with that email " })
}
const hashedPassword = user.getPassword
const createdAt = user.createdAt
const userId = user._id
const secret = hashedPassword + "-" + createdAt
const token = jwt.sign({ userId }, secret, {
expiresIn: 3600
})
const url = getResetPasswordURL(user, token)
const emailTemplate = resetPasswordTemplate(user, url)
const sendEmail = () => {
transporter.sendMail(emailTemplate, (err, info) => {
if (err) {
res.status(500).json("Error sending email")
}
console.log("email sent", info.response)
})
}
sendEmail()
} catch (error) {
res.status(500).json({ error })
}
}
}
This is the route which is throwing the above error:
router.post("/reset-password/:email", emailController.createOneTimeTokenAndSendMail)
I have been dealing with errors like these constantly, so I'd like to clear my doubts once and for all.

Categories