I am having an issue with user authentication aftre sign in. This is part of expanding my knowledge and now I am stuck with this.
My code looks like this:
Route:
const express = require('express');
const router = express.Router();
/* import controllers */
const {
findById,
isAuth,
isAdmin,
} = require('../controllers/user.controller.js');
const { requierSignin } = require('../controllers/auth.controller.js');
router.param('userId', findById);
router.get('/test/:userId', requierSignin, isAuth, findById, (req, res) => {
res.json({ user: req.profile });
});
Here i have sign in, find user by id and my authorization method. This is a test route.
As i goes for my controllers:
User:
const User = require('../models/user.models');
//user middleware
exports.findById = async (req, res, next, id) => {
try {
let user = await User.findById(id).exec();
if (!user) {
return res.status(401).json({
errors: [
{
msg: 'User not found',
},
],
});
}
req.profile = user; // this will get user profile based on User
next();
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
/* check if user is authenticated */
exports.isAuth = (req, res, next) => {
/* id we have user that will send id and is auth */
let user = req.profile && req.auth && req.profile._id == req.auth._id;
console.log(req.profile);
console.log('auth', req.auth);
console.log('id', req.profile._id);
console.log('user', user);
if (!user) {
return res.status(403).json({
errors: [
{
msg: 'Access Denied',
},
],
});
}
next();
};
and my sign in method from auth.controller
const config = require('config');
const jwt = require('jsonwebtoken'); //generate token
const expressJwt = require('express-jwt'); //auth check
const User = require('../models/user.models');
const secret = config.get('jwtSecret');
exports.requierSignin = expressJwt({
secret,
userProperpty: 'auth',
});
This is only part of that code but if rest will be neede I will update it
As far i get. User sign in and the getting a profile is working. Bu I want to protect other user profiles with isAuth. This is getting me "Access denied". From console log i got
auth undefined
id 5eaac004200b95869cc76531
user undefined
GET /api/test/5eaac004200b95869cc76531 403 88.591 ms - 36
But when i change in my isAuth method user to be only:
let user = req.profile
user is defined.
Not sure what I am missing here :/ Not sure why i don't get in my 'auth' ._id and this is causing issues
github repo
Got this working
Issue was in my user sign in method:
const payload = {
user: {
id: user._id,
email: user.email,
name: user.name,
role: user.role,
},
};
return res.json({
token,
payload,
});
In my payload i sending
id:user._id
But when i generted a token i was doing sending id
// generate a token with id and secret
const token = jwt.sign({ _id: _id }, secretJwt);
So every time when myt prtected rout was checking id from token and user
let user = req.profile && req.auth && req.profile._id == req.auth._id;
This was false as there was no id to compare.
Setting _id to id solved my issue
// generate a token with id and secret
const token = jwt.sign({ _id: _id }, secretJwt);
That was a brain cracker for me.
Even I'm facing the same issue using "postman", as "req.profile" property has to be set from frontend only if the user is "signedIn"
req.profile = user
so we need to populate the user first
exports.setUserInfo = function setUserInfo(request) {
const getUserInfo = {
_id: request._id,
firstName: request.profile.firstName,
lastName: request.profile.lastName,
email: request.email,
role: request.role
};
return getUserInfo;
};
git repo https://github.com/NRSingh007/mern-starter/blob/master/server/helpers.js
Related
i have a problem when i try to use a private api in my node.js server, This is the process that i am following
SignUp (if the user doesn't have an account) or logIn (already have an account).
It generates the token and i pass it in the header res.header('access_token': token)
I copy this token and paste it in my private api in the header section (i'm using postman to test it for now) in order to verify if the user is logged in.
In my route, i use a middleware to turn it private, validating if the user can use the resource jwt.verify(authorization[1], process.env.SEED_AUTH) (SEED_AUTH is my token secret stored in my server)
Here is when i have the error, the middleware is failling to verify the user and throw me this error jwt expired
This is my route
const UsersController = require('../controllers/users.controller')
const AuthorizationMiddleware = require('../middlewares/authorization.middleware')
exports.routesConfig = (app) => {
app.post('/api/users',[
AuthorizationMiddleware.verifyValidJWT
], UsersController.insert)
}
This is my middleware
const jwt = require('jsonwebtoken')
require('../config/env.config')
exports.verifyValidJWT = (req, res, next) => {
if (req.headers['access-token']) {
try {
let authorization = req.headers['access-token'].split(' ');
if (authorization[0] !== 'Bearer') {
return res.status(401).json({
ok: false,
err: "Unauthorized, Need a valid token"
});
} else {
console.log(authorization[1]);
req.jwt = jwt.verify(authorization[1], process.env.SEED_AUTH);
return next();
}
} catch (error) {
return res.status(403).json({
ok: false,
err: "Forbidden, need a valid token -> " + error.message
});
}
} else {
return res.status(401).json({
ok: false,
err: "Need to recieve a valid token"
});
}
}
And finally the API UsersController.insert
What i'm trying to do with this api is to create a new user.
For a better understanding this is my LOGIN API
const User = require('../models/users.model')
const { signUpValidation, logInValidation } = require('../middlewares/auth.validation.data')
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')
exports.logIn = async (req, res) => {
let body = req.body;
//Validation
const { error } = logInValidation(body)
if (error) {
return res.status(400).json({
ok: false,
err: error.details[0].message
})
}
//Check if the email already exists in the database
const user = await User.findOne({ email: body.email })
if (!user) {
return res.status(400).json({
ok: false,
err: "Invalid Email or password!"
})
}
const validPass = await bcrypt.compareSync(body.password, user.password)
if (!validPass) {
return res.status(400).json({
ok: false,
err: "Invalid Email or password!"
})
}
const token = jwt.sign(
{
_id: user._id,
email: user.email
},
process.env.SEED_AUTH,
{
expiresIn: process.env.TOKEN_EXPIRY
}
)
res.header('access-token', token).json({
ok: true,
user: {
id: user._id,
email: user.email
}
});
}
SignUp and LogIn validation
I use these middleware to verify if it is a valid email, and the name with a minimum number of letters...
My process.env.TOKEN_EXPIRY is set to 300 (i understand that, it is in seconds), i've tried with bigger number though
(The API works without the middleware).
What would be the problem that i am not seeing. Thanks for your help.
process.env variables are as string and not as a number. According to the jsonwebtoken documentation, string is considered as milliseconds by default and number is counted as seconds by default. So change TOKEN_EXPIRY to 300000 from 300
I'm creating social network app with MERN. I have implemented followers and following, and now i'm trying to get the list of posts of only users that i'm following. So, if I console.log my req.user I only get the 'id' and 'iat', but i need more information of a user such as following array.
here is what i have:
auth.js middlware:
const config = require('config')
const jwt = require('jsonwebtoken')
function auth(req, res, next) {
const token = req.header('x-auth-token')
// check for token
if (!token) return res.status(401).json({ msg: 'Unauthorized token' })
try {
// verify token
const decoded = jwt.verify(token, config.get('jwtSecret'))
// add user from payload
req.user = decoded
console.log('---',decoded)
next()
} catch (e) {
return res.status(400).json({ msg: 'Token is not valid' })
}
}
module.exports = auth
auth.js
const express = require('express')
const router = express.Router()
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const config = require('config')
const User = require('../models/User')
const auth = require('../middleware/auth')
// #route POST /api/auth
// #desc Login user
// #access Public
router.post('/', (req, res) => {
const { email, password } = req.body
// filed validation
if (!email || !password) res.status(400).json({ msg: 'Fields cannot be empty.' })
// Check if user is registered
User
.findOne({ email })
.then(user => {
if (!user) res.status(400).json({ msg: `User doesn't exist.` })
//console.log(user)
// validate password
bcrypt
.compare(password, user.password)
.then(isMatch => {
if (!isMatch) return res.status(400).json({ msg: 'Invalid password' })
// if password matches, send token and user
jwt.sign(
{ id: user.id },
config.get('jwtSecret'),
(err, token) => {
if (err) throw err
res.json({
token,
user: {
id: user.id,
email: user.email,
first_name: user.first_name,
last_name: user.last_name,
registration_date: user.registration_date,
profile_image: user.profile_image,
user_bio: user. user_bio,
followers: user.followers,
following: user.following
}
})
}
)
})
})
})
and this is my console log
Server started at port 5000
[0] --- { id: '5efccfb13224e1489439bcfd', iat: 1593626545 }
[0] * { id: '5efccfb13224e1489439bcfd', iat: 1593626545 }
EDIT:
// #route GET /api/posts/subscribedPost
// #desc get all subscribed post
// #access Private
router.get('/subscribedPost', auth, (req, res) => {
console.log('*',req.user) // not working correctly, have only id and iat
Post
.find({userID: req.user.id})
.populate('userID', 'first_name last_name profile_image _id')
.sort({ registration_date: -1 })
.then(post => res.json(post))
.catch(err => res.json(err))
})
This is because you are signing your JWT token with id only, so when u decode it you will only get id
jwt.sign({ id: user.id },config.get('jwtSecret'),(err, token) =>{})
Here you are just passing id, you can pass your whole user object to get all the data
jwt.sign(user.toJSON(),config.get('jwtSecret'),(err, token) =>{})
PS- YOU MIGHT NOT WANT TO PASS YOUR WHOLE USER, JUST DID IT FOR DEMO
You can customize to what values you want.
You need to get user data DB in auth middleware and then add it to req.user. So if there are any changes to the user you get updated user data.
eg:
function auth(req, res, next) {
const token = req.header('x-auth-token')
// check for token
if (!token) return res.status(401).json({ msg: 'Unauthorized token' })
try {
// verify token
const decoded = jwt.verify(token, config.get('jwtSecret'))
// get the user from DB
const user = User.findById(decoded.id) //only populate fields which are required
req.user = user
console.log('---',decoded)
next()
} catch (e) {
return res.status(400).json({ msg: 'Token is not valid' })
}
}
I'm developing a Node.JS & MongoDB app inserting articles and categories, with a signup and login users system. I need to add/fix a secret string on order to make Jsonwebtoken (JWT_KEY) work properly.
My authentication or authorization fails when I try to add an article with details (title, attached picture ect.) threw Postman, probably because I made a mistake installing or using the jsonwebtoken library. It maybe a mistake in the nodemon.json file that is should be hidden at the end (user, password, JWT_KEY), but maybe in another part of my code.
The Postman process connects with the article.js routes file, that seems to be fine. The relevant part is the createArticle POST, since the rest work fine so far:
const express = require('express');
const router = express.Router();
const upload = require('../middlewares/upload');
const checkAuth = require('../middlewares/checkAuth');
const {
getAllArticles,
createArticle,
getArticle,
updateArticle,
deleteArticle
} = require('../controllers/articles');
router.get('/', getAllArticles);
router.get('/:articleId', getArticle);
router.post('/', checkAuth, upload.single('image'), createArticle);
router.patch('/:articleId', checkAuth, updateArticle);
router.delete('/:articleId', checkAuth, deleteArticle);
module.exports = router;
Here is the authChek.js middleware that is responsible of the authorization process:
const jwt = require('jsonwebtoken');
const checkAuth = (req, res, next) => {
try {
const token = req.headers.authorization.split('')[1];
jwt.verify(token, process.env.JWT_KEY);
next();
} catch(error) {
res.status(401).json({
message: 'Auth failed'
})
}
}
module.exports = checkAuth;
The verify seems ok and should work fine connecting to nodemon. If it's all fine, Postman should return back a message that the authorization succeeded - but it returns failed auth. Here, in the article.js controller, the POST method seems fine to and should not catch an error of 500, 401 or 409:
const mongoose = require('mongoose');
const Article = require('../models/article');
const Category = require('../models/category');
module.exports = {
createArticle: (req, res) => {
const { path: image } = req.file;
const { title, description, content, categoryId } = req.body;
Category.findById(categoryId).then((category) => {
if (!category) {
return res.status(404).json({
message: 'Category not found'
})
}
const article = new Article({
_id: new mongoose.Types.ObjectId(),
title,
description,
content,
categoryId,
image: image.replace('\\','/')
});
return article.save();
}).then(() => {
res.status(200).json({
message: 'Created article'
})
}).catch(error => {
res.status(500).json({
error
})
});
}
}
Another file using the JWT_KEY is the users.js controller, in the login part. Look at the area of the if & result. It may fail to connect properly to the .env part of the nodemon.json file. See here "process.env.JWT_KEY":
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
module.exports = {
login: (req, res) => {
const { email, password } = req.body;
User.find({ email }).then((users) => {
if (users.length === 0) {
return res.status(401).json ({
message: 'Authentication failed'
});
}
const [ user ] = users;
bcrypt.compare(password, user.password, (error, result) => {
if (error) {
return res.status(401).json({
message: 'Authentication failed'
});
}
if (result) {
const token = jwt.sign({
id: user._id,
email: user.email,
},
process.env.JWT_KEY,
{
expiresIn: "1H"
});
return res.status(200).json({
message: 'Authentication successful',
token
})
}
res.status(401).json({
message: 'Authentication failed'
});
})
})
}
}
Is there something to fix here? Or how can I check if my JWT_KEY in nodemon.json is written properly or wrong? If the string is generated by a library or taken from somewhere else, I don't know where to search for it in my app or around the web.
I want to get authorized user data. But instead I get the data of a completely different user. How to write a function getProfile to display the data of the current user?
controllers/auth.js:
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const db = require('../config/db.config.js')
const User = db.user
module.exports.login = async function(req, res) {
const candidate = await User.findOne({
where: {
username: req.body.username
}
})
if (candidate) {
const passwordResult = bcrypt.compareSync(req.body.password, candidate.password)
if (passwordResult) {
const token = jwt.sign({
username: candidate.username,
userId: candidate._id
}, process.env.SECRET_OR_KEY, {expiresIn: 60 * 60})
res.status(200).json({
token: `Bearer ${token}`
})
} else {
res.status(401).json({
message: 'Passwords do not match. Try again.'
})
}
} else {
res.status(404).json({
message: 'User with this login was not found.'
})
}
}
module.exports.getProfile = async function(req, res) {
try {
const user = await User.findOne({id: req.body.id})
res.status(200).json(user)
} catch(e) {
errorHandler(res, e)
}
}
routes/auth.js:
const express = require('express')
const router = express.Router()
const controller = require('../controllers/auth')
const passport = require('passport')
router.post('/login', controller.login)
router.get('/profile', passport.authenticate('jwt', {session: false}), controller.getProfile)
module.exports = router
You should attach a signed token in each HTTP req from client, either by custom HTTP header or set in cookie. This token is sent only after successful login which contains user's id and other info.
After you start receiving that token you can validate it (checking for expiry or some manual change) using a middleware and that token data will be the actual user data belongs to the user loggedin.
Now, you read that header/cookie to get requester user's info and you can then send their respective data only.
Let's say if client is sending you token info in header called tkn. Your token validation can be as follows:
var jwt = require('jsonwebtoken');
const SECRET = 'whatulike';
function verifyToken(req, res, next) {
var token = req.headers.tkn || "";
if (!token.length)
return unauthorised(res, 'Token absent');
jwt.verify(token, SECRET, function(err, decoded) {
if (err)
return unauthorised(res, 'Failed to authenticate token.');
req.tkn = decoded.id;
next();
});
}
function unauthorised(res, msg){
const sc = 401;
logger.warn(`${sc} - Unauthorised request ${res.req.originalUrl}`);
res.status(sc).send({msg});
}
module.exports.verifyToken = verifyToken;
And at handler side you can read tkn data like:
module.exports.getProfile = async function(req, res) {
try {
const user = await User.findOne({id: req.tkn.userId})
res.status(200).json(user)
} catch(e) {
errorHandler(res, e)
}
}
I am trying to make get request to my protected routes using bearer token and it returns unautorized, even after sending token through header.
I am using bearer token on nodejs, expressjs app using mlab remote database
I registered new user, then I logged with that email and it sent me back a token(as expected).
When I sent this token through header of other route without login it returns Unautorized.
my steps are
1) registered with new email
2) login request successful
3) failed get request to route localhost:5000/api/users/current, and returns Unautorized.
user.js file has
// users.js for authentication and authorization
const express = require("express");
const router = express.Router();
const gravatar = require("gravatar");
const bcrypt = require("bcryptjs");
const keys = require("../../config/keys");
const jwt = require("jsonwebtoken");
const passport = require("passport");
// Load User Model to check existing email is used for registration or not?
const User = require("../../models/User");
// #route GET request to api/users/test
// #description Tests users route
// #access Public, without login
router.get("/test", (req, res) => res.json({ msg: "Users Works" }));
// #route GET request to api/users/register
// #description new registration of user.
// #access Public, without login first register
router.post("/register", (req, res) => {
User.findOne({ email: req.body.email }).then(user => {
if (user) {
return res.status(400).json({ email: "Email value exists already." });
} else {
console.log("no user found of this email in DB");
const avatar = gravatar.url(req.body.email, {
s: "200", //Size of gravatar in pixels
r: "pg", //rating,
d: "mm" //default value= 'mm'
});
// create user
const newUser = new User({
name: req.body.name,
email: req.body.email,
avatar,
password: req.body.password
});
// gensalt(noOfSalts_of_Iterations,(err,salt_result)=>{})
bcrypt.genSalt(10, (err, salt) => {
// hash(plaintext,salt,callback(err,resultant ciphertext))
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) {
console.log("error in bcrypt.hash()");
throw err;
}
//assign salted hash to password
newUser.password = hash;
// Save new password in datebase, overriding plaintext;
newUser
.save()
.then(user => res.json(user)) // if yes,then send it as argument in brackets.
.catch(err =>
console.log("Error occured in saving hash password in DB\n")
);
});
});
}
});
});
// #route GET request to api/users/login
// #description Login/signing-in registered user. return JWT token
// #access Public
router.post("/login", (req, res) => {
const email = req.body.email;
const password = req.body.password;
// find user to match it's password
User.findOne({ email: req.body.email }).then(user => {
//check if no user
if (!user) {
return res.status(404).json({ email: "User's email found." });
}
// else if do this..
// if user's email-id is found then match it's password-hash with local-database
bcrypt.compare(password, user.password).then(isMatch => {
if (isMatch) {
// user pswd matched => then return JWT token back for authentication
// res.json({ msg: "Success" });
const payload = { it: user.id, name: user.name, avatar: user.avatar };
// created JWT token
// now sign token
// jwt.sign(payload, secretKey, expire-time, callback );
// jwt.sign
jwt.sign(
payload,
keys.secretOrKey,
{ expiresIn: 3600 },
(err, token) => {
res.json({
success: true,
token: "bearer " + token
});
}
);
} else {
// pswd doesn't matched
return res.status(400).json({ password: "Password didn't match" });
}
});
});
});
// #route GET request to api/users/current - current user with token
// #description Return current user
// #access Private, can't go without login
router.get(
"/current",
passport.authenticate("jwt", { session: false }),
(req, res) => {
res.json({ msg: "Success" });
}
);
module.exports = router;
data is stored succesfully at remote db mlab, but I can't figureout what's problem.
my Github repo of this project is this
Please Look at the Line of your code Users.js Line 88 the payload id is stored in 'it'
const payload = { it: user.id, name: user.name, avatar: user.avatar };
But in your passport.js
User.findById(jwt_payload.id)
you are extracting it by id so if you use here jwt_payload.it the you will get message success