JWT Full Stack Verify Token Not Working to Frontend - node.js

I have an API in which uses VerifyToken authentication with JWT. This works through postman, however it appears there's an issue passing this through to the frontend to ensure the token is verified.
For one, I have a code block to create verifyToken:
const verifyToken = (req, res, next) => {
const authHeader = req.headers.token;
if (authHeader) {
const token = authHeader.split(" ")[1];
jwt.verify(token, process.env.JWT_SEC, (err, user) => {
if (err) res.status(403).json("Token is not valid!");
req.user = user;
next();
});
} else {
return res.status(401).json("You are not authenticated!");
}
};
If I run the following in Postman, it works all good, header and all.
localhost:5000/api/users/updateUser/62a9be62a8262145b72feee9
This is then handled in requestMethods,
import axios from "axios";
const BASE_URL = "http://localhost:5000/api/";
const user = JSON.parse(localStorage.getItem("persist:root"))?.user;
const currentUser = user && JSON.parse(user).currentUser;
const TOKEN = currentUser?.accessToken;
export const publicRequest = axios.create({
baseURL: BASE_URL,
});
export const userRequest = axios.create({
baseURL: BASE_URL,
bearer: { token: `Bearer ${TOKEN}` },
});
However when I pass this to the frotnend, for example through a request like this,
const updateUser = async () => {
//include all in the state earlier with spread
//update only position, which is input
userRequest.put(`users/updateUser/${user._id}`, userData);
console.log('your data has updated to', userData)
//include the put request here for API
}
I am now getting an error for a 401: Error: Request failed with status code 401
It appears the token isn't being passed to the frontend correctly. What am I doing wrong?

Passing token in headers in Axios is not tuned correctly. Notice the headers config.
export const userRequest = axios.create({
baseURL: BASE_URL,
headers: { token: `Bearer ${TOKEN}` },
});
Another way to pass the token is where you are calling the API:
const updateUser = async () => {
//include all in the state earlier with spread
//update only position, which is input
userRequest.put(`users/updateUser/${user._id}`, userData, {
headers: {token: `Bearer ${TOKEN}`}
});
console.log('your data has updated to', userData)
//include the put request here for API
}
Here is another tip: in your auth middleware by detecting a wrong token, do not go to next and return!
const verifyToken = (req, res, next) => {
const authHeader = req.headers.token;
if (authHeader) {
const token = authHeader.split(' ')[1];
jwt.verify(token, process.env.JWT_SEC, (err, user) => {
if (err) {
res.status(403).json('Token is not valid!');
return;
} else {
req.user = user;
next();
}
});
} else {
return res.status(401).json('You are not authenticated!');
}
};

Related

Jwt authorization always giving 403 Forbidden

I am a beginner with node js. I want to make an authentication server using jwt (jsonwebtoken).
The problem is when I test my end point "/api/posts?authorisation=Bearer token..." in postman with method POST with the right token, it gives me forbidden.
Here is my code:
const express = require('express')
const jwt = require('jsonwebtoken')
const app = express()
app.get("/api", (req, res) => {
res.json({
message: "Hey there!!!"
})
})
app.post('/api/posts', verifyToken, (req, res) => {
jwt.verify(req.token, "secretkey", (err, authData) => {
if (err) {
res.sendStatus(403) //forbidden
res.send(`<h2>${err}</h2>`)
} else {
res.json({
message: "Post Created...",
authData
})
}
})
})
app.post('/api/login', (req, res) => {
const user = {
id: 1,
username: "John",
email: "john#gmail.com"
}
jwt.sign({ user: user }, "secretkey", (err, token) => {
res.json({
token
})
})
})
function verifyToken(req, res, next) {
const bearerHeader = req.headers["authorization"]
if (typeof bearerHeader !== "undefined") {
const bearerToken = bearerHeader.split(" ")[1]
req.token = bearerToken
next()
} else {
res.sendStatus(403) //forbidden
}
}
app.listen(5000, () => {
console.log("Server is running :)")
})
I expected it to work because I brought it from a tutorial.
Your code works
The problem is in your request invocation:
According to the oauth2 spec, the Authorization token should be a header and your code expect that
So the token should be sent as http header, not as a query param like foo/bar?authorization=Bearer token...".
Here some samples
Postman
Axios (javascript)
let webApiUrl = 'example.com/getStuff';
let tokenStr = 'xxyyzz';
axios.get(webApiUrl,
{ headers: { "Authorization": `Bearer ${tokenStr}` } });
Advice
Read about oauth2 and jwt
Perform the token validation in the middleware to avoid the validation on each route

Cannot post Token from Client

Frontend Code
<script>
const formDOM = document.querySelector(".form");
const btnSubmitDOM = document.querySelector(".btn-submit");
// get token form server
formDOM.addEventListener("submit", async (e) => {
e.preventDefault();
const email = document.querySelector("#email").value;
const password = document.querySelector("#password").value;
try {
const { data } = await axios.post(
"http://localhost:3000/api/v1/auth/login",
{ email, password }
);
console.log("token", data.token);
localStorage.setItem("token", data.token);
} catch (error) {
console.log(error);
}
});
// after submit, if authenticated, goes to protective route
btnSubmitDOM.addEventListener("click", async () => {
const token = localStorage.getItem("token");
console.log("toekn when press", token);
try {
const { data } = await axios.get("http://localhost:3000/api/v1/inventories", {
headers: {
Authorization: `Bearer ${token}`,
},
});
location.assign("/inventories");
} catch (error) {
localStorage.removeItem("token");
console.log(error);
}
});
</script>
Here is the backend code
const jwt = require("jsonwebtoken");
const auth = async (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer")) {
res.send("Auth route - Unauthenticated Error");
}
const token = authHeader.split(" ")[1];
console.log("token is ", token);
try {
const payload = jwt.verify(token, 'secret');
// console.log(payload);
// attached to the inventories route
req.user = {
userId: payload.userId,
email: payload.email,
};
// console.log(req.user)
next();
} catch (error) {
console.log(error);
}
};
module.exports = auth;
Here is the error from server side
token is eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2MzY4ZjFiM2U1ODJlYmQ1MjJkODI4MGEiLCJlbWFpbCI6Imt5YXdAZ21haWwuY29tIiwiaWF0IjoxNjY3ODI3NzIzLCJleHAiOjE2NzA0MTk3MjN9.rptgRrYB4TrLQCxbB18JqMHd05LSox-LQuiLJS0L2Gw
/Users/kyawmyo/Desktop/software-development/nodejs-&-vanilajs-projects/inventories/middleware/authentication.js:8
const token = authHeader.split(" ")[1];
^
TypeError: Cannot read properties of undefined (reading 'split')
I try to test with postman, all working fine.
But I send request from client side, it is error, I cannot figure it out. Please help.

Why do I get an authentication error when I pass an axios config object with an authorization token? MERN

I am making a small social network application. I came up with a like post route and the user has to be logged in to be able to perform that action, and I have auth middleware that looks like this:
const auth = async (req, res, next) => {
// check header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer')) {
throw new UnauthenticatedError('Authentication invalid');
}
const token = authHeader.split(' ')[1];
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = { userId: payload.userId };
next();
} catch (error) {
throw new UnauthenticatedError('Authentication invalid');
}
};
When I pass an object with an authorization token to Axios, I get an error, here is the action.js file:
export const likePost = id => async (dispatch, getState) => {
try {
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.put(`/api/v1/post/${id}/like`, config);
dispatch({ type: POST_LIKE_SUCCESS, payload: data });
} catch (error) {
console.log(error);
dispatch({
type: POST_LIKE_FAIL,
payload: { msg: error.response.data.msg },
});
}
};
When I open the network tab in the browser, the request tab shows that I forward the headers {Authorization: Bearer .... token} and in response I get error 401.
I'm no expert with Axios, but according to the documentation, the put method uses data as the second argument and config as the third.
Maybe try to provide an empty value like null or an empty string for the second argument:
const { data } = await axios.put(`/api/v1/post/${id}/like`, null, config);

[ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client in MERN Stack Application

How to make user redirect after authentication based on user.role ?
I'm getting the following error: UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
const jwt = require('jsonwebtoken')
const { COOKIE_NAME, SECRET } = require('../config/config')
module.exports = function() {
return (req, res, next) => {
let token = req.cookies[COOKIE_NAME]
if(token) {
jwt.verify(token, SECRET, function(err, decoded){
if (err) {
res.clearCookie(COOKIE_NAME)
} else {
if(decoded.user.role === 'admin') {
res.redirect('http://localhost:4000')
}
req.user = decoded;
}
})
}
next();
}
}
Login Fetch:
fetch(`${API}/auth/login`,{
method: 'POST',
credentials: 'include',
withCredentials: true,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(user)
})
.then((response) => {
if(response.status === 302) {
window.location = 'http://localhost:4000'
}
else if(response.status === 200) {
onSuccess()
setTimeout(() => {
window.location = '/'
}, 1000)
} else if (response.status === 401) {
onError()
}
})
.catch((error) => {
console.log(error)
})
}
Here is my authService:
const jwt = require('jsonwebtoken')
const User = require('../models/User');
const bcrypt = require('bcrypt')
const { SALT_ROUNDS, SECRET } = require('../config/config');
const register = async ({name, username, email, password, cart}) => {
let salt = await bcrypt.genSalt(SALT_ROUNDS);
let hash = await bcrypt.hash(password, salt);
const user = new User({
name,
username,
email,
password: hash,
cart
});
return await user.save()
}
const login = async ({email, password}) => {
let user = await User.findOne({email})
if (!user) {
throw {message: 'User not found!'}
}
let isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw {message: 'Password does not match!'}
}
let token = jwt.sign({user}, SECRET)
return token;
}
And my authController:
const { Router } = require('express');
const authService = require('../services/authService');
const { COOKIE_NAME } = require('../config/config');
const router = Router();
router.post('/login', async (req, res) => {
const {email, password} = req.body
try {
let token = await authService.login({email, password})
res.cookie(COOKIE_NAME, token)
res.status(200).json(token)
} catch (error) {
res.status(401).json({ error: error })
}
})
Here is my server if this will help:
app.use((req, res, next) => {
const allowedOrigins = ['http://localhost:3000', 'http://localhost:4000'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', true)
}
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
Since you're using jwt.verify with a callback, it is being executed asynchronously. Due to this, immediately after calling verify but before getting the decoded token, your next() function is called which passes the control to the next middleware (which probably would be synchronous) which then returns the request.
The flow of events would be something like this:
if(token) { ... starts
jwt.verify(token, ... is called asynchronously. It registers the callback function(err, decoded) { ... but doesn't execute it yet.
You exit the if(token) { ... } block and call next().
The next middleware in line starts executing and probably returns the request if it is the last middleware in chain. So the client has already been sent the response by this time.
jwt.verify(token ... succeeds and calls your registered callback.
It sees that there is no error at line if (err) ... so it moves to the else block.
It decodes the user role and tries to redirect (which internally would try to insert a header on the response). But this fails because the user was already sent the response (and hence your error message).
So the simple solution to this is to not call next() UNTIL jwt verifies and decodes your token and you know the role. In the code below, I've moved the next() function call a few lines upwards.
const jwt = require('jsonwebtoken')
const { COOKIE_NAME, SECRET } = require('../config/config')
module.exports = function() {
return (req, res, next) => {
let token = req.cookies[COOKIE_NAME]
if(token) {
jwt.verify(token, SECRET, function(err, decoded){
if (err) {
res.clearCookie(COOKIE_NAME)
} else {
if(decoded.user.role === 'admin') {
res.redirect('http://localhost:4000')
}
req.user = decoded;
}
next();
})
}
}
}

Securely access route with JWT and localstorage

I'm building a small application where a user logs in and gets redirected to /profile. Right now, I fetch the JWT from localstorage and check it via the server. The server then sends it back to the client to tell me if it's a valid session or not.
jQuery/Client:
UserController.initPanel = () => {
if (session === null) {
window.location = "/";
} else {
UserController.requestAuth(session);
}
};
UserController.requestAuth = (sessionToken) => {
var settings = {
"url": "/api/auth",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": `Bearer ${sessionToken}`,
},
"data": ""
}
$.ajax(settings).done(function (response) {
console.log(response);
});
};
Node.js/auth.js route:
router.post("/", (req, res) => {
const authHeader = req.headers.authorization;
if (typeof authHeader !== 'undefined') {
const bearerToken = authHeader.split(' ')[1];
verifyToken(bearerToken, (authData) => {
tokenRequest(authData, (authResponse) => {
handleAuthResponse(req, res, authResponse);
})
});
}
});
const handleAuthResponse = (req, res, authResponse) => {
console.log(authResponse);
return res.status(200).json(authResponse);
}
const verifyToken = (token, cb) => {
jwt.verify(token, 'mysecret', (err, authData) => {
if (err) {
res.sendStatus(403)
} else {
cb(authData);
}
});
}
const tokenRequest = (authHeader, cb) => {
//console.log(authHeader);
var config = {
headers: {'Authorization': `bearer ${authHeader.token}`}
};
axios.get('https://myapi.dev/api/session/me', config)
.then((res) => {
if (res.data.error) {
return response.data
} else {
cb(res.data);
}
})
.catch((error) => {
console.log('error', error);
});
}
I feel like this isn't the correct way to do it. I'm rendering templates with ejs:
router.get("/profile", (req, res) => {
const settings = {
title: "Profile",
revslider: false
};
res.render("profile/profile", { settings: settings } );
});
And if for some reason, JS is disabled, /profile is still accessible. Which isn't that big of a problem, it just feels wrong.
So, is it possible to access /profile route, securely checking for authorization server-side first, before rendering?
Also, auth.js returns some user data I could use in the .ejs template. So that's another reason I'd like to try check auth before rendering as well.
EDIT:
Auth middleware, which I didn't use because I wasn't sure how to pass in the token?
module.exports = (req, res, next) => {
try {
const decoded = jwt.verify(req.body.token, 'mysecret');
req.token = decoded;
} catch (error) {
console.log(error);
return res.status(401).json({
message: 'Auth Failed'
});
}
next();
}
Very basic middleware implementation below which leverages express and express-session.
We basically create a simple function to check req.session exists, within that object, you could have something that identifies whether the user has actually authenticated. I'd recommend you add your own logic here to further check the user status.
const authCheckMiddleware = (req, res, next) => {
// Perform auth checking logic here, which you can attach
// to any route.
if(!req.session) {
return res.redirect('/');
}
next();
};
The authCheckMiddleware can be attached to any route, with app.use or router.use. The req object is passed to all middleware.
// Use the authCheckMiddleware function
router.use('/profile', authCheckMiddleware);
Your router.get('/profile') call is now protected by the above middleware.
// Route protected by above auth check middleware
router.get("/profile", (req, res) => {
const settings = {
title: "Profile",
revslider: false
};
res.render("profile/profile", { settings: settings } );
});

Resources