Next JS express jwt authentication - node.js

I've implemented a /api/login to login a user. I also have a page login.js, that sends the form data to the login API. My login api checks the user credentials in my database and then signs a token and saves it to a cookie.
My /api/login:
await authCollection.findOne(
{
authCode: req.body.authcode,
},
function(err, doc) {
if (err) throw err;
if (!doc) return res.status(400).send('auth not found');
if (doc) {
const token = jwt.sign({ _id: doc._id }, process.env.TOKEN_SECRET);
res
.status(200)
.cookie('token', token, {
maxAge: 2 * 60 * 60 * 1000,
httpOnly: false,
})
.send(token);
}
},
);
I can protect my api routes with passing in a middleware like this:
function(req, res, next) {
const token = req.cookies.token || '';
if (!token) {
return res.status(401).send('Access denied');
}
try {
const decrypt = jwt.verify(token, process.env.TOKEN_SECRET);
req.user = {
id: decrypt.id,
firstname: decrypt._id,
};
next();
} catch (err) {
res.status(400).send('Invalid token');
}
}
I do not understand how I can protect normal pages, which are in my /pages directory. Could somebody give me a hint in the right direction? I have already googled and read each page, but couldn't implement

It depends, what I've done is to write an hoc privateArea which returns new component that fetches users accessToken from the API, then decodes it, if user is logged in, renders the wrapped page, otherwise, renders "restricted area" message.
export function privateArea(PageComponent) {
const PrivateArea = ({ accessToken, pageProps }) => {
const user = decodeToken(accessToken);
if (user) {
return <PageComponent {...pageProps} />;
} else {
return <RestrictedArea />;
}
};
PrivateArea.getInitialProps = async (...args) => {
const accessToken = await api.getAccessToken();
if (typeof PageComponent.getInitilProps === 'function') {
const pageProps = await PageComponent.getInitilProps(...args);
}
return {
accessToken,
pageProps: pageProps || {},
};
};
return PrivateArea;
}

Related

BCrypt does not compare password correctly

I am currently working on an API on node.js. I have seen other similar posts but the solution does not work for me. Below are my codes to retrieve user by email and compare the password. I enter the correct credentials but the compare is always returning false. Am I missing out something?
const bcrypt = require('bcrypt');
const saltRounds = 10;
app.post('/auth', function (req, response) {
let query = `select * from users where email = "${req.body.email}"`;
console.warn(req.body.email);
databaseConnector.query(query, (error, result) => {
if (error) {
console.log(error, 'Error occurred with /auth/ API...');
}
if (result.length > 0) {
console.warn(req.body.password, result[0].password);
bcrypt.compare(req.body.password, result[0].password, function (err, res) {
console.warn(res, 'bcryot response')
// if res == true, password matched
// else wrong password
if (res) {
var token = jwt.sign({ userID: result.id }, 'todo-app-super-shared-secret', { expiresIn: '2h' });
response.send({
token: token,
id: result[0].id,
firstName: result[0].firstName
})
}
else {
return response.sendStatus(401);
}
});
}
else {
return response.sendStatus(401);
}
})
});

Getting an undefined token in backend

Hello i am trying to use my token in my application after user is logged in but am getting an undefined response in my console. Below are my codes. How can i correct my code to be able to access token inside application and use to do other features of the application?
my controller
import User from "../models/user";
import Stripe from "stripe";
const stripe = Stripe(process.env.STRIPE_SECRET);
export const createConnectAccount = async (req, res) => {
console.log(req.user);
try {
const user = await User.findById(req.user._id).exec();
console.log("USER ==> ", user);
if (!user.stripe_account_id) {
const account = await stripe.accounts.create({
type: "express",
});
console.log("ACCOUNT ===>", account);
user.stripe_account_id = account.id;
user.save();
}
} catch (error) {
res.status(500).json();
}
};
my middleware
var { expressjwt: jwt } = require("express-jwt");
// req.user
export const requireSignin = jwt({
//secret, expiryDate
secret: process.env.JWT_SECRET,
algorithms: ["HS256"],
});
my routes
import express from "express";
const router = express.Router();
import { requireSignin } from "../middlewares";
import { createConnectAccount } from "../controllers/stripe";
router.post("/create-connect-account", requireSignin, createConnectAccount);
module.exports = router;
my auth controller
import User from "../models/user";
import jwt from "jsonwebtoken";
export const register = async (req, res) => {
console.log(req.body);
const { name, email, password } = req.body;
if (!name) return res.status(400).send("Name is required");
if (!password || password.length < 6)
return res
.status(400)
.send("Password is required and should be minimum 6 characters long");
let userExist = await User.findOne({ email }).exec();
if (userExist) return res.status(400).send("Email is taken");
const user = new User(req.body);
try {
await user.save();
console.log("User saved successfully", user);
return res.json({ ok: true });
} catch (err) {
console.log("CREATE USER FAILED", err);
return res.status(400).send("Error.Try again");
}
};
export const login = async (req, res) => {
// console.log(req.body);
const { email, password } = req.body;
try {
//check if user with credentials
let user = await User.findOne({ email }).exec();
// console.log("USER EXISTS", user);
if (!user) res.status(400).send("User with email not found");
//compare password
user.comparePassword(password, (err, match) => {
console.log("COMPARE PASSWORD IN LOGIN ERR", err);
if (!match || err) return res.status(400).send("Wrong password");
//("GENERATE A TOKEN THEN SEND AS RESPONSE TO CLIENT");
let token = jwt.sign({ _id: user._id }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
res.json({
token,
user: {
_id: user._id,
name: user.name,
email: user.email,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
},
});
});
} catch (err) {
console.log("LOGIN ERROR", err);
res.status(400).send("Signin failed");
}
};
my terminal output
POST /api/login 200 1142.309 ms - 349
undefined
POST /api/create-connect-account 500 9.092 ms - -
Headers
import axios from "axios";
export const createConnectAccount = async (token) => {
await axios.post(
`${process.env.REACT_APP_API}/create-connect-account`,
{},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
};
I'm sorry to tell you your code has other errors in it.
My guess is that your res is not well written in auth controller, login function :
res.status(201).json({
token :token,
user: user
})
Also when reading your token trying to authenticate : it will be easier to use the same package than the one that sign it.
const jwt = require("jsonwebtoken");
exports. requireSignin = () => {
return async (req, res, next) => {
try {
const token = req?.headers?.authorization?.split(" ")[1];
const decodedToken = jwt.verify(token, process.env.JWT_SECRET);
const userId = decodedToken._id;
const user = await User.findOne({ _id: userId });
if (user) {
req.auth = {
user: user,
};
} else {
throw new Error("user not found");
}
next();
} catch (error) {
console.log(error.message);
res.status(401).json({ error: "failed to authenticate" });
}
};
};
But your code is pretty hard to read :
To make it easier to read and clearer for you, try and use joy or yup
Joi : https://www.npmjs.com/package/joi
Yup : https://www.npmjs.com/package/yup
With those you will be able to create middlewares to avoid wrong entries in your body : for example
if (!name) return res.status(400).send("Name is required");
is processed automatically with those packages
Also, you shouldn't use 'import' and 'require' in the same project, choose either one of them
I hope this will help

How do I forward authorization with the user JWT token in the header and compare it to the JWT token in the cookie to Auth user?

I have a problem with the logged in user, when I refresh the page the user gets lost. This is how I assign a JWT token:
const signToken = id => {
return jwt.sign({ id }, 'my-ultra-secure-and-ultra-long-secret', {
expiresIn: '14d',
});
};
This is how I send a token to a cookie with this function as well:
const createSendToken = (user, statusCode, res) => {
const token = signToken(user._id);
const cookieOptions = {
expires: new Date(Date.now() + 14 * 1000 * 60 * 24),
httpOnly: true,
};
res.cookie('jwt', token, cookieOptions);
// Remove password from output
user.password = undefined;
res.status(statusCode).json({
status: 'success',
token,
data: {
user,
},
});
};
This is my login controller:
exports.login = catchAsync(async (req, res, next) => {
const { email, password } = req.body;
// 1) Check if email and password exist
if (!email || !password) {
return next(new AppError('Please provide email and password!', 400));
}
// 2) Check if user exists && password is correct
const user = await User.findOne({ email }).select('+password');
if (user && (await user.correctPassword(password, user.password))) {
createSendToken(user, 200, res);
} else {
return next(new AppError('Incorrect email or password', 401));
}
});
This is my Protect controller (protect middleware):
exports.protect = catchAsync(async (req, res, next) => {
// 1) Getting token and check of it's there
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return next(
new AppError('You are not logged in! Please log in to get access.', 401)
);
}
// 2) Verification token
const decoded = await promisify(jwt.verify)(
token,
'my-ultra-secure-and-ultra-long-secret'
);
// 3) Check if user still exists
const currentUser = await User.findById(decoded.id);
if (!currentUser) {
return next(
new AppError(
'The user belonging to this token does no longer exist.',
401
)
);
}
// 4) Check if user changed password after the token was issued
if (currentUser.changedPasswordAfter(decoded.iat)) {
return next(
new AppError('User recently changed password! Please log in again.', 401)
);
}
// GRANT ACCESS TO PROTECTED ROUTE
req.user = currentUser;
res.locals.user = currentUser;
next();
});
This is my private route with this middleware:
router.route('/:id').get(authController.isLoggedIn, postController.getPost);
The problem is when I log in I get a cookie, but I can't access the protected route (I get an error token is undefind). When I refresh the page the user is lost but the cookie remains in storage. When I try to access the protect route via postman and when I add the Authorization Bearer to the header ..... (token) I can access it.
This is my frontend user reducer:
export const userLoginReducer = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return { loading: true, isAuthenticated: false };
case USER_LOGIN_SUCCESS:
return {
loading: false,
isAuthenticated: true,
user: action.payload,
};
case USER_LOGIN_FAIL:
return { loading: false, isAuthenticated: false, error: action.payload };
case USER_LOGOUT:
return { loading: false, isAuthenticated: false, user: null };
default:
return state;
}
};
This is my user action:
export const login = (email, password) => async dispatch => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const { data } = await axios.post(
'/api/v1/users/login',
{ email, password },
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
This is my login screen:
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const redirect = location.search ? location.search.split('=')[1] : '/';
const dispatch = useDispatch();
const userLogin = useSelector(state => state.userLogin);
const { loading, error, isAuthenticated } = userLogin;
console.log(isAuthenticated);
useEffect(() => {
if (isAuthenticated) {
history.push(redirect);
}
if (error) {
console.log(error);
}
}, [isAuthenticated, history, redirect, error]);
const submitHandler = e => {
e.preventDefault();
dispatch(login(email, password));
};
I stuck here about 2 days trying to complete this. Please someone help me :)
As you are setting the jwt token in a cookie, you can read it from there when making a request. You can send the cookie automatically with axios by adding withCredentials: true in your axios config object, like:
axios({
withCredentials: true
})
On the server side, you can get the jwt cookie value by looking it on the req.cookies object, like:
let token;
if (req.cookies.jwt) {
token = req.cookies.jwt;
}

How to get current user in another file while working on postman and node.js

I have this code for login. How do I use the current user's information from this code to another file using postman and node.js?
exports.loginUser = (req,res, next) => {
User.find({email: req.body.email})
.exec()
.then(user => {
if(user.length < 1) {
return res.status(401).json({
message: 'Auth failed'
});
}
bcrypt.compare(req.body.password, user[0].password, (err ,result) => {
if(err){
return res.status(401).json({
message: 'Auth failed'
});
}
if (result) {
const token = jwt.sign({
email: user[0].email,
userId: user[0]._id
},
process.env.JWT_KEY ,
{
//options
expiresIn: "1h"
});
You should tell exactly what you want, what you said is confusing, but If you mean how to pass the logged in user to the next middleware, you gotto assign the user to req
exports.loginUser = async (req, res, next) => {
const user = await User.find({ email: req.body.email }).exec()
if (user.length < 1) {
return res.status(401).json({
message: 'Auth failed'
});
}
bcrypt.compare(req.body.password, user[0].password, (err, result) => {
if (err) {
return res.status(401).json({
message: 'Auth failed'
});
}
if (result) {
const token = jwt.sign({
email: user[0].email,
userId: user[0]._id
},
process.env.JWT_KEY, {
//options
expiresIn: "1h"
});
req.user = user[0];
return next();
}
})
}
Then in the next middleware you have access to logged in user, using req.user.
UPDATE:
To implement the functionality that you want, according to what you described in the comment:
Before anything import these packages:
const jwt = require("jsonwebtoken");
const { promisify } = require("util");
First you implement a route that checks for credentials and sends back a signed jwt:
exports.login = CatchAsync(async(req, res, next) => {
const { email, password } = req.body;
if (!email || !password) {
return next(new Error("Please provide email and password"));
}
const user = await UserModel.findOne({email});
if (!user) {
return next(new Error("There is no user with that email"));
}
if(!(await bcrypt.compare(password, user.password))) {
// actually the pass is not correct but for security reasons we don't say that
return next(new Error("Email or password is not correct");
}
// pass the user id to jwt so later can identify user
const token = jwt.sign({ id: user._id }, 'yourJwtSecret', {
expiresIn: '90d',
});
// httpOnly prevents access to token in client's browser, so it is safe
const cookieOptions = {
expires: new Date(
Date.now() + 90 * 24 * 60 * 60 * 1000
),
httpOnly: true,
};
res.cookie("jwt", token, cookieOptions);
res.status(200).json({
status: 'success',
message: 'logged in successfully'
});
});
Then for every route that needs to check for logged In user, use this middleware:
exports.isLoggedIn = CatchAsync(async(req, res, next) => {
// Check if there is a token
// if no token was provided it means user is not logged in
let token;
if (req.cookies.jwt) {
token = req.cookies.jwt;
} else {
return next();
}
// Verify token
// decoded now has access to id of user
let decoded;
try {
decoded = await promisify(jwt.verify)(token, 'yourJwtSecret');
} catch (err) {
// if token was modified or expired or not valid
return next();
}
// get the user
const user = await UserModel.findOne({
_id: decoded.id
});
// access granted, user is logged in
req.user = user; // you can access the logged in user in the next middleware
res.locals.user = user; // you can access the logged in user in template engines
next();
});
If the user is not logged in, req.user won't be assigned. therefore in next middlewares if req.user was undefined you know user is not logged in.
for more info jwt docs.
If you have never taken any NodeJs course, I'd recommend this course

errors handling on login with req.flash

I have an issue with not getting just flash message on my page, but getting json with an error instead of it:
However, if I click "back" in browser, I see the page as expected, with flash message on it
My request code:
const handleLogin = async (req, res) => {
const { errors, isValid } = validateLoginInput(req.body);
if (!isValid) {
return res.status(422).json(errors);
}
const { email, password } = req.body;
const user = await User.findOne({email});
if (!user) {
errors.email = AUTH_ERROR;
req.flash('loginMessage', AUTH_ERROR);
return res.status(404).json(errors);
}
const isMatch = user.validatePassword(password, user.password);
const { id, role } = user;
if (isMatch) {
const payload = {id, email, role};
jwt.sign(
payload,
config.JWTAuthKey,
{expiresIn: 3600},
(err, token) => {
res.cookie(tokenCookieName, token, { maxAge: 60 * 60 * 24 * 7 , httpOnly: false });
res.redirect('/');
}
);
} else {
errors.password = AUTH_ERROR;
req.flash('loginMessage', AUTH_ERROR);
return res.status(403).json(errors);
}
};
In addition, my passport config (I use jwt strategy)
const config = (passport) => {
passport.use(
new Strategy(opts, (jwt_payload, done) => {
User.findById(jwt_payload.id)
.then((user) => {
if (user) {
return done(null, user);
}
return done(null, false);
})
/*eslint no-console: ["error", { allow: ["warn", "error"] }] */
.catch(err => console.error(err));
}),
);
};
Any ideas would be highly appreciated, thank you un advance.
It turned out to be pretty easy. No need to send back status, just
return res.redirect('/')
will do the trick. One can redirect wherever is needed.

Resources