How to protect routes using JWT in NextJS? - api

I was working on a project in next js where i need to protect some routes. I was trying to do this using jwt but for some reason it's not working, the POST method below inside index.js is injecting new entry in db bypass the verify token middleware. Any kind of help would be greatly appreciated.
verifyAuthToken.middleware.js
import jwt from "jsonwebtoken";
export default function verifyAuthToken(req, res) {
try {
const token = req.headers;
if (!token) {
return res.status(401).json({
success: false,
msg: "You are not authorized to access this route",
});
}
const { userId } = jwt.verify(
req.headers.authorization,
process.env.TOKEN_SECRET
);
if (!userId) {
return res.status(401).json({
success: false,
msg: "You are not authorized to access this route",
});
}
req.userId = userId;
} catch (error) {
res.status(401).json({
success: false,
msg: "You are not authorized to access this route",
error: error.message,
});
}
}
index.js
import verifyAuthToken from "../../../middleware/verifyAuthToken.middleware";
import Evoluter from "../../../models/Evoluter.model";
import dbConnect from "../../../server-utils/connectDB";
import { sendEmail } from "../../../server-utils/sendEmail";
import baseURL from "../../../utils/baseURL";
export default async (req, res) => {
const { method } = req;
verifyAuthToken(req, res);
await dbConnect();
switch (method) {
// #route GET api/evoluters
// #desc Create new evoluter
// #access Private
case "POST":
try {
const { name, email } = req.body;
const evoluter = await Evoluter.findOne({ email: email.toLowerCase() });
if (evoluter) {
return res.status(400).json({
success: false,
msg: "Evoluter already exists",
});
}
const newEvoluter = await Evoluter.create({
name,
email: email.toLowerCase(),
});
try {
await sendEmail({
email: newEvoluter.email,
subject: "Welcome to Rubrica",
message: `Hi ${newEvoluter.name}, welcome to Rubrica. Please click on the link below to activate your account. ${baseURL}/home/${newEvoluter._id}`,
});
res.status(201).json({
success: true,
data: newEvoluter,
msg: "Evoluter created successfully. Please check your email to activate your account.",
});
} catch (error) {
console.log(error);
res.status(500).json({
success: false,
msg: "Error sending invitation email, please let the admin know.",
});
}
} catch (error) {
res.status(400).json({
success: false,
msg: "Sorry, we couldn't send the invitation. Please try again.",
error: error,
});
}
break;
default:
res.status(400).json({
success: false,
msg: "Invalid request",
});
break;
}
};
Here we can see that the verify token middleware was fired as expected, however the data inside the body got injected into the DB which we don't really need.
Postman output
Please let me know if you need more information I don't have much experience asking questions in stackoverflow.

It is bypassing the verifyAuthToken function because You are doing it anyway you are just generating response on verifyAuthToken
You can return a boolean at the end of your verifyAuthToken something like this:
if (userId) return true;
else return false;
Then in your index.js before injecting data to database instead of verifyAuthToken(req, res); do this:
if (!verifyAuthToken(req, res)) {
return;
}
You can also use NextJs middleware

Related

Express can't set headers after they are sent to the client

I have the following code:
router.post('/:email/addWorkflow', async function (req, res, next) {
const params = req.params;
const workflow = req.body;
const email = params.email;
User.findOne({ email: email }, function (err, user) {
if (err) {
res.status(500).send({
error: 'Error while querying database'
});
} else if (user) {
const workflows = user.workflows;
workflows.forEach(wf => {
if (wf) {
if (wf.workflowId === workflow.workflowId) {
res.status(409).send({
error: 'Workflow with that id already exists'
});
}
}
});
workflows.push(workflow);
User.updateOne({ email: email }, { $set: { workflows: workflows } }, { upsert: false }, function (err) {
if (err) {
res.status(500).send({
message: 'Error while updating database'
});
} else {
res.status(200).send({
message: 'Wf added successfully'
});
}
});
} else {
res.status(404).send({
message: 'No such user'
});
}
});
});
After I make a post with an already existing workflowId, I get the following error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:485:11)
..........
at /home/petar/Documents/jsProjects/p/backend/routes/users.js:50:29
at CoreDocumentArray.forEach (<anonymous>)
at /home/petar/Documents/jsProjects/p/backend/routes/users.js:47:17
at /home/petar/Documents/jsProjects/p/backend/node_modules/mongoose/lib/model.js:4915:16
at /home/petar/Documents/jsProjects/p/backend/node_modules/mongoose/lib/model.js:4915:16
at /home/petar/Documents/jsProjects/linear-mixed-models/backend/node_modules/mongoose/lib/query.js:4380:11
[... lines matching original stack trace ...]
at processTicksAndRejections (internal/process/task_queues.js:76:11) {
code: 'ERR_HTTP_HEADERS_SENT'
Any ideas? I looked at other posts for the same error. I understand that it happens if I try to send response 2 time: res.send({...}) and res.send({...}). However, this does not happen in my case. Thanks in advance
I am not completely sure what line the error message is indicating, but the following loop is the only place I can think of a multiple response on your code
workflows.forEach(wf => {
//foreach is looping
if (wf) {
if (wf.workflowId === workflow.workflowId) {
res.status(409).send({
error: 'Workflow with that id already exists'
});
//but I don't think this guy will stop looping after the first "send()"
}
}
});

How do I handle passport js redirects from Nuxt SSR?

I am using Nuxt SSR with express session and I have a passport JS redirect from the server side
/**
* POST /signup
* Create a new local account.
*/
exports.postSignup = (req, res, next) => {
const validationErrors = [];
if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: 'Please enter a valid email address.' });
if (!validator.isLength(req.body.password, { min: 8 })) validationErrors.push({ msg: 'Password must be at least 8 characters long' });
if (req.body.password !== req.body.confirmPassword) validationErrors.push({ msg: 'Passwords do not match' });
if (validationErrors.length) {
req.flash('errors', validationErrors);
return res.redirect('/signup');
}
req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false });
const user = new User({
email: req.body.email,
password: req.body.password
});
User.findOne({ email: req.body.email }, (err, existingUser) => {
if (err) { return next(err); }
if (existingUser) {
req.flash('errors', { msg: 'Account with that email address already exists.' });
return res.redirect('/signup');
}
user.save((err) => {
if (err) { return next(err); }
req.logIn(user, (err) => {
if (err) {
return next(err);
}
res.redirect('/');
});
});
});
};
If I call the redirect method? it would reload the page and clear Vuex state right?
How do I do this redirect from passport such that Vuex state is kept intact and client page does not refresh
It is indeed better to asynchronously handle form submissions to avoid page refresh as #Darius mentioned. But for completion's sake I'd like to mention that solutions do exist to persist your Vuex state, such as vuex-persistedstate.
It can be used to persist the state to localStorage, sessionStorage, or even cookies. It can also be used as a Nuxt plugin.

Bcrypt + Sequelize password not saving as hash in DB

Sequelize + Bcrypt not storing passwords in DB as hash
As the title says, whenever I attempt to store a user into my SQLite DB the console outputs the password as a hash but when I look into the DB with DBbrowser I can see the plaintext password.
Model
// const Promise = require('bluebird')
const bcrypt = require('bcrypt')
async function hashPassword (user, options) {
if (!user.changed('password')) {
return 0
}
const SALT_FACTOR = 8
await bcrypt.hash(user.password, SALT_FACTOR, (err, hash) => {
if (err) {
console.log(err)
}
// user.setDataValue('password', hash)
user.password = hash
console.log(user)
})
}
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
unique: true
},
password: DataTypes.STRING
}, {
hooks: {
beforeSave: hashPassword,
beforeCreate: hashPassword
}
})
User.prototype.comparePassword = function (password) {
bcrypt.compare(password, this.password, function (res, err) {
if (res) {
console.log(res)
} else {
console.log(err)
}
})
return bcrypt.compare(password, this.password)
}
return User
}
Controllers
module.exports = {
async register (req, res) {
try {
const user = await User.create(req.body)
const userJson = user.toJSON()
res.send({
user: userJson,
token: jwtSignUser(userJson)
})
} catch (err) {
// e-mail already exists or such
res.status(400).send({
error: 'This email address is already in use'
})
}
},
async login (req, res) {
try {
// Grab user input
const { email, password } = req.body
const user = await User.findOne({
where: {
email: email
}
})
// Check to see if user is in db
if (!user) {
res.status(403).send({
error: 'the login information was incorrect / Not Found'
})
}
// Check to see if password is valid
const isPasswordValid = await user.comparePassword(password)
if (!isPasswordValid) {
return res.status(403).send({
error: 'The login information was incorrect'
})
}
// return user using toJSON()
const userJson = user.toJSON()
res.send({
user: userJson,
token: jwtSignUser(userJson)
})
} catch (e) {
res.status(500).send({ error: 'An error occured attempting to login' })
console.log(e)
}
}
}
To elaborate a little more, whenever I create a user, I receive the following:
{
"user": {
"id": 1,
"email": 'test#test.com",
"password": "$2b$08$SYYXU/GDSCFsp3MVeuqrduI0lOLHeeub7whXiaMMoVxO53YJry.1i",
"updatedAt": "2018-09-07T22:44:12.944Z",
"createdAt": "2018-09-07T22:44:12.944Z"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJTVVBCUkhVQGxvbC5jb20iLCJwYXNzd29yZCI6IiQyYiQwOCRTWVlYVS9HRFNDRnNwM01WZXVxcmR1STBsT0xIZWV1Yjd3aFhpYU1Nb1Z4TzUzWUpyeS4xaSIsInVwZGF0ZWRBdCI6IjIwMTgtMDktMDdUMjI6NDQ6MTIuOTQ0WiIsImNyZWF0ZWRBdCI6IjIwMTgtMDktMDdUMjI6NDQ6MTIuOTQ0WiIsImlhdCI6MTUzNjM2MDI1MywiZXhwIjoxNTM2OTY1MDUzfQ.mDaeIikzUcV_AGTuklnLucx9mVyeScGpMym1y0kJnsg"
}
Which to me says the DB successfully hashed my password, and stored it. The overhanging issue for me with this is the fact that I believe it's causing the bcrypt.compare function to spit out 'false'. As always, any insight or help would be greatly appreciated!
I'm pretty sure that this answer is too late for you, but might help others landing on this same question.
The main issue I can see is how you are using the async/await pattern. Changing this:
async function hashPassword (user, options) {
if (!user.changed('password')) {
return 0
}
const SALT_FACTOR = 8
await bcrypt.hash(user.password, SALT_FACTOR, (err, hash) => {
if (err) {
console.log(err)
}
// user.setDataValue('password', hash)
user.password = hash
console.log(user)
})
}
to this, worked for me:
async function hashPassword(user, options) {
if (!user.changed("password")) {
return 0;
}
user.password = await bcrypt.hash(user.password, SALT_FACTOR);
}
Can you please try to add only one hook
hooks: {
beforeSave: hashPassword,
}
Because I think your password is getting hashed two times. as beforeSave and beforeCreate both hooks get executed.
Hope it helps

undefined jwt token react/express

I am implementing a JWT authentication on a login/registration system. When there is a successful login/registration I am setting a user token in localStorage.
Problem is when I check my localStorage the user key is present but the value is undefined. I think the issue might be in my axios post or in my express file but I can't quite figure it out.
// my action creator
export function login(user, history) {
return async (dispatch) => {
axios.post('/api/login', { user })
.then(res => {
dispatch({ type: AUTHENTICATED });
localStorage.setItem('user', res.data.token);
history.push('/');
})
.catch((error) => {
dispatch({
type: AUTHENTICATION_ERROR,
payload: 'Invalid email or password'
});
});
};
}
The data is reaching my api correctly. The item is being set but the value res.data.token is undefined.. Below is my express file
// login.js (/api/login)
router.post('/', function(req, res) {
var email = req.body.user.email;
var password = req.body.user.password;
// TODO: create db file and import connection
var connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "",
database: "dbname",
port: 3307
});
connection.connect(function(err) {
if(err) {
console.log(err);
} else {
connection.query("SELECT ID, Password FROM Users WHERE Email = ?", [email], function(err, result) {
if(err) {
console.log('Could not find account');
res.send(err);
} else {
var id = result[0].ID;
bcrypt.compare(password, result[0].Password, function(err, result) {
if(result) {
console.log(id);
res.json({ id });
} else {
console.log('Incorrect password');
}
});
}
});
}
});
});
Since the res.data.token in my action creator is returning undefined does that mean the response in my express file ( res.json([id]) ) is just returning defined?
You are not sending the response properly.
res.json([id]); Its just sending the array of id. That's why res.data.token is undefined. as data does not contain an object.
Send proper object like:
res.json({id});

Using async in express

Below is my rest API endpoint /signup. The problem I'm having now is that the endpoint does not stop after validateEmail. Even after it failed email form-validation and res.send() is done, the endpoint continues. So I'm keep getting the error 'Error: Can't set headers after they are sent.'. I would like to be able to finish the endpoint inside its functions like validateEmail , checkEmailInUse, makeUser, and so on.
router.post("/signup", async (req, res, next) => {
const { email, password } = req.body;
const users = req.app.get("users");
validateEmail(res, email);
await checkEmailInUse(res, users, email);
const user = await makeUser(res, users, email, password);
res.send({ message: "POST signup request OK", user });
});
function validateEmail(res, email) {
const isEmail = emailFilter.test(email);
if (!isEmail) {
res.status(400).send({
error: {
message: "Requested email is not email type",
type: "FormatValidation",
location: "validateEmail"
}
});
return;
}
}
async function checkEmailInUse(res, users, email) {
const query = { email };
try {
const user = await users.findOne(query);
if (user) {
res.send({ message: "The email is already used" });
}
} catch (err) {
res.status(400).send({
error: {
message: "Failed to find user",
type: "DatabaseError",
location: "checkEmailInUse"
}
});
return;
}
}
The code keeps going after a failed validate because you call:
validateEmail(res, email);
and then your code just keeps going. This is normal flow of control in Javascript. Your function keeps executing lines of code until you return in the function. The same issue is true for checkEmailInUse(). If you want to sometimes send the response inside those functions and be done, then you need a return value from those functions that you can check and then use if statements to determine whether your code should do more or not.
Following your style of sending the error response inside the validation functions (which is not how I would probably structure things), you could return values from those functions and test those return values in the request handler like this:
router.post("/signup", async (req, res, next) => {
const { email, password } = req.body;
const users = req.app.get("users");
if (validateEmail(res, email)) {
if (await checkEmailInUse(res, users, email)) {
const user = await makeUser(res, users, email, password);
res.send({ message: "POST signup request OK", user });
}
}
});
function validateEmail(res, email) {
const isEmail = emailFilter.test(email);
if (!isEmail) {
res.status(400).send({
error: {
message: "Requested email is not email type",
type: "FormatValidation",
location: "validateEmail"
}
});
return false;
}
return true;
}
async function checkEmailInUse(res, users, email) {
const query = { email };
try {
const user = await users.findOne(query);
if (user) {
res.send({ message: "The email is already used" });
return false;
} else {
return true;
}
} catch (err) {
res.status(400).send({
error: {
message: "Failed to find user",
type: "DatabaseError",
location: "checkEmailInUse"
}
});
return false;
}
}
}
But, I think you might find this is simpler if you get rid of the local functions because then when you send a response, you can just directly return from the main function and be done. Here's how that could look:
router.post("/signup", async (req, res, next) => {
function err(res, message, type, location) {
res.status(400).send({error: {message, type, location}});
}
const { email, password } = req.body;
if (!emailFilter.test(email)) {
err(res, "Requested email is not email type", "FormatValidation", "validateEmail");
return;
}
const users = req.app.get("users");
try {
const user = await users.findOne({email});
if (user) {
res.send({ message: "The email is already used" });
return;
}
} catch(e) {
err(res, "Failed to find user", "DatabaseError", "checkEmailInUse");
return;
}
try {
const user = await makeUser(res, users, email, password);
res.send({ message: "POST signup request OK", user });
} catch(e) {
err(res, "Failed to make user", "DatabaseError", "makeUser");
}
}