I have a small node application that uses express. In order for a user to submit form data, they must log in. When they log in, they get a json web token that should get passed to the auth middleware to verify that they can submit the form.
I am running into trouble sending the JWT to the auth middleware.
The Authorization when the user logs in and then gets sent to the page to submit the form looks like this:
router.post('/auth', async (req, res, next) => {
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let user = await User.findOne({ email: req.body.email });
if (!user) return res.status(400).send('Invalid email or password.');
const validPassword = await bcrypt.compare(req.body.password, user.password);
if (!validPassword) return res.status(400).send('Invalid email or password.');
const token = user.generateAuthToken();
res.set({'x-auth-token': token }).render('../views/index/form', { 'x-auth-token': token })
;});
The route for the form submission looks like this:
router.post('/rates', auth, async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let rates = new Rates({
// deleted to save space
});
rates = await rates.save();
res.send(rates);
});
As you can see in order to post, it has to go through the auth middleware first which looks like this:
const config = require('config');
const jwt = require('jsonwebtoken');
function auth(req, res, next) {
const token = req.header('x-auth-token');
if (!token) return res.status(401).send('Access denied. No token provided.')
try {
const decoded = jwt.verify(token, config.get('jwtPrivateKey'));
req.user = decoded;
next();
}
catch (ex) {
res.status(400).send('Invalid token.');
}
}
module.exports = auth;
I get the "Access denied. No token provided." every single time. When I do a post request to rates in Postman and set the headers there, it works fine.
I am sending this form data through a page rendered with express handlebars.
I'm not sure if I am using res.set the wrong way, or if something gets lost when the handlebars form view gets rendered?
I can see the x-auth-token when I log in on the front end, get directed to the form page, and view the headers in chrome inspector. But once I try to submit the form data for the rates in the front end, the auth token is lost and it fails at the auth middleware.
There has to be something I'm just not seeing.
Here is my code on the page that is sending the form data:
<script>
var formElement = document.querySelector("form");
var header = XMLHttpRequest.getResponseHeader("x-auth-token");
var xhr = new XMLHttpRequest();
xhr.open('POST', '/rates', true);
xhr.setRequestHeader("x-auth-token", header);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
if(this.readyState == XMLHttpRequest.DONE && this.status = 200) {
console.log("success");
} else {
console.log("error sending")
}
}
xhr.send(new FormData(formElement));
</script>
Related
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());
I am performing authentication and authorization using JWT and building rest apis to connect ejs and backend. After getting a person authenticated i am rendering to the blogger page of that user but when i clink on add block it says no token is passed but when i am doing it using postman then there is no issue it is getting token then.
this is my code of rendering a blogger page after authentication:
router.post('/', async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let user = await User.findOne({email:req.body.email});
if (user) return res.status(400).send("user already registered");
user = new User(_.pick(req.body,['name','email','password']));
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password,salt);
await user.save();
// const token= user.generateAuthToken();
// res.header('x-auth-tocken',token).send({name:user.name, user:user._id,token:token});
const token = jwt.sign({_id:this._id},config.get('jwtPrivateKey'));
let blogs = await blogss.find();
res.header('x-auth-token',token).render('bhome',{blogs:blogs,user:user});
})
and this is my auth middleware:
module.exports = function (req ,res, next) {
const token = req.header('x-auth-token');
console.log(req.header('x-auth-token'));
console.log('me here in auth');
if(!token) return res.status(401).send('access denied because there is no token');
try {
const decoded = jwt.verify(token,config.get('jwtPrivateKey'));
req.user = decoded;
next();
} catch (ex) {
res.status(400).send('invalid taken');
}
}
and this is the route after authentication which says token is not availible:
router.get('/addblog', auth, (req, res)=>{
res.render('addblog');
});
The way you are handling the header is wrong and based on the limited code you provided, it seems to be the reason for possible error. Instead of this doing the hard way, try using this gist and implement it as a util function in your frontend
I have a get request that should return all of the logged-in user's project they created, I believe the code was written well. when I run it on postman I consistently get a 401 unauthorised error, but when I change the request to patch for example, and I also run a patch request on the postman, it works properly. what could be the issue?
// get all logged in user's projects
router.get('/api/project/mine', auth, async (req, res) => {
try {
const id = req.user._id
const projects = await Project.find({owner: id})
res.status(200).send(projects)
} catch (e) {
res.status(401).send()
}
})
the auth middleware
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, 'creativetoken')
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })
if (!user) {
throw new Error()
}
req.token = token
req.user = user
next()
} catch (e) {
res.status(401).send({ error: 'Please authenticate' })
}
}
module.exports = auth
Note: the auth makes sure the objectId of the logged-in user is returned through req.user.id
You have 2 try-catch that return 401 but you don't log the error or return the error to frontend.
You need to add console.log(e) in 2 catch block in your auth middleware and your get request.
try {
// some of your code
} catch (e) {
console.log(e); //Add this line
res.status(401).send({ error: 'Please authenticate' })
}
I have some troubles understanding this code.
I have the following express login route , it sends an accesstoken as a a response, and then in my frontend i save it in a cookie, and sends a refresh token as... (?) <- i dont understand this phase
app.post('/login', async (req, res) => {
const { email, password} = req.body;
try{
//find user in database if not then error
const user = fakeDB.find(user => user.email === email);
if(!user) throw new Error("User doesnt exist");
//compare the passwords and see if they match send error if not
const valid = await compare(password, user.password);
if(!valid) throw new Error("Password not corect");
//create refresh and access token if its correct
const accesstoken = createAccessToken(user.id);
const refreshtoken = createRefreshToken(user.id);
//put the refreshtoken in the "database"
user.refreshtoken = refreshtoken;
console.log(fakeDB);
//send token refreshtoken as a cookie, and accesstoken as a regular response
sendRefreshToken(res, refreshtoken);
sendAccessToken(res, req, accesstoken);
}catch (err){
res.send({
error: `${err.message}`
})
}
})
The functions to send the refreshtoken and the accesstoken are the following
const sendAccessToken = (res , req, accesstoken) => {
res.send({
accesstoken,
email: req.body.email
})
}
const sendRefreshToken = (res, refreshtoken) => {
res.cookie("refreshtoken", refreshtoken, {
httpOnly: true,
path: '/refresh_token'
})
}
And then I have a /refresh_token route to refresh my token , adn this is where im lost, I see that `` const token = req.cookies.refreshtoken is being extracted from a cookie, but... how is that happening if the cookie is httponly , isnt it supposed to be non readable? The issue there is that I dont understand how to get that refreshtoken value if im calling that route from the frontend. It somehow gets it in the backend if I use postman, but in my frontend I have no idea how to get it.
app.post('/refresh_token', (req, res) => {
const token = req.cookies.refreshtoken
//if no token in request
if(!token) return res.send({accesstoken : ''});
//if we have a token we verify it
let payload = null;
try{
payload = verify(token, process.env.REFRESH_TOKEN_SECRET);
}catch(err){
return res.send({accesstoken: ''});
}
//if token is valid check if user exist
const user = fakeDB.find(user => user.id === payload.userId)
if(!user) return res.send({ accesstoken: ''});
//if user exists check if refreshtoken exist on user
if(user.refreshtoken !== token){
return res.send({accesstoken: ''})
}
//if token exist create a new Refresh and Accestoken
const accesstoken = createAccessToken(user.id);
const refreshtoken = createRefreshToken(user.id);
user.refreshtoken = refreshtoken;
//send new refreshtoken and accesstoken
sendRefreshToken(res, refreshtoken);
return res.send({accesstoken});
})
My node JS application involves setting a Jason Web Token to the response header at login so that I can use this JWT to verify the user when accessing restriced pages.
My question is how to get this header to persist. Currently I am setting the header at registration and login routes using code such as this:
// Login Handle
router.post("/login", async (req, res, next) => {
const user = await User.findOne({ email: req.body.email });
if (!user) return res.status(400).send("Email or password is incorrect");
const validPassword = await bcrypt.compare(req.body.password, user.password);
if (!validPassword) return res.status(400).send("Invalid email or password.");
const token = user.generateAuthToken();
res.set('x-auth-token', token).redirect('/dashboard');
});
Here is my authentication middleware that I am trying to get working:
function auth(req, res, next) {
const token = req.header('x-auth-token');
if(!token) return res.status(401).send("Access denied. No token provided");
// if the token is valid it will return the decoded payload
try {
const decoded = jwt.verify(token, jwtPrivateKey);
req.user = decoded;
next();
}
catch(ex) {
res.status(400).send("Invalid token.");
}
I can see in the network tab on chrome dev tools that the header is correctly being set with the JWT. However, as soon as the page is refreshed or I visit a different page the header disappears.
Do I need to use res.set('x-auth-token', token).render('foo') on every route? It seems like this wouldn't be a great solution.
Can anyone tell me what I am doing wrong?