How to get logged in user? req.user is undefind - node.js

How to get logged in user in express app. I want to know witch user create post. This is my Post.js model:
const postsSchema = mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
image: {
type: String,
required: true,
},
category: {
type: String,
required: true,
},
numLikes: {
type: Number,
required: true,
default: 0,
},
comments: [commentSchema],
},
{
timestamps: true,
}
);
This is my authUser function where i log in user with email and password:
const authUser = async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user && (await user.matchPassword(password))) {
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: generateToken(user._id),
});
} else {
res.status(401);
throw new Error('Invalid email or password');
}
};
generateToken function is JWT:
import jwt from 'jsonwebtoken';
const generateToken = id => {
return jwt.sign({ id }, 'abc123', {
expiresIn: '30d',
});
};
export default generateToken;
When i create post i want to know user who created it, this is my create post function:
const createPost = async (req, res) => {
const post = new Post({
user: req.user._id,
title: 'Sample Title',
description: 'Sample Desc',
image: '/images/sample.jpeg',
category: 'Sample Category',
numLikes: 0,
});
const createPost = await post.save();
res.status(201).json(createPost);
};
When i try to create post i got this error in console:
UnhandledPromiseRejectionWarning: TypeError: Cannot read property '_id' of undefined.
I can log in with postman, i can register, i can get user by id. How to tell my app Hey i am logged in user and have access to req.user object

You need to have the client send the token back to you, which you then validate (typically via a middleware affecting some section of endpoints so you don't have to call a validation function in individual endpoints).
If instead, express is also your front end, then you need to use a library like express-session https://www.npmjs.com/package/express-session to manage cookies. A good example is available on their page:
// Use the session middleware
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
// Access the session as req.session
app.get('/', function(req, res, next) {
if (req.session.views) {
req.session.views++
res.setHeader('Content-Type', 'text/html')
res.write('<p>views: ' + req.session.views + '</p>')
res.write('<p>expires in: ' + (req.session.cookie.maxAge / 1000) + 's</p>')
res.end()
} else {
req.session.views = 1
res.end('welcome to the session demo. refresh!')
}
})
Otherwise you've sent the token to client and done nothing with it.

Do you need of a middleware like this:
module.exports = (req, res, next) => {
// Authorization token example: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWQiOiIxIiwiaWF0IjoxNTE2MjM5MDIyfQ.dYo0kOIhum5mMTRV8CAn8gQ_6aqoDQLE--vCZD4E-fg
const { authorization } = req.headers
if (!authorization) return res.send({ message: 'Token not provided', code: 400 })
const [ schema, token ] = authorization.split(' ')
if (schema !== 'Bearer') return res.send({ message: 'Token is bad formated', code: 400 })
if (!token) return res.send({ message: 'Invalid token', code: 400})
jwt.verify(token, 'abc123', (error, decoded) => {
if (error) return res.send({ message: error.message, code: 401})
req.userId = decoded.id
})
next()
}
Hope this is helpful for you.

The first thing you should do is to send token back to the client or attach cookies to your response.
After which you set up a middleware that will check cookies or token in your case using jwt.verify(token, jwtSecret). That will return the id and all other things you stored in the token, then you then store them in req.user, where you will be able to access the details later.
//if you stored token in cookies -
const {accessToken} = req.signedCookies
const payload = isTokenValid(accessToken) //verify the token
req.user = payload.user;
return next();
//if you stored in auth header
const bearerToken = req.headers.authorization
//bearerToken = "Bearer token"
const token = bearerToken.split(" ").join(",")[1]
//verify the token using jwt
const payload = isTokenValid(token)
req.user = payload
return next()

Related

How can I fix : "Cannot read properties of undefined( reading 'role')" I believe its a mongodb/mongoose issue. How can I fix database request issue?

I am currently getting problems reading role on my postman POST request on backend nodejs express. I was wondering how would i go about fixing this?
below is the route for authentication in my middleware/auth.js
exports.isAuthenticatedUser = catchAsyncErrors(async (req, res, next) => {
const { token } = req.cookies
if (!token) {
return next(new ErrorHandler('Login first to access this resource.', 401))
}
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.user = await User.findById(decoded.id);
next()
})
// Handling users roles
exports.authorizeRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(
new ErrorHandler(`Role (${req.user.role}) is not allowed to acccess this resource`, 403))
}
next()
}
}
I have added in extra information. Hopefully this helps clear up some missing background info. But the ideas is to show you where ```decoded.id`` comes from (getJwtToken). which by the way is used in the registration process of user accounts. which I also share at the bottom.
addition to models/users.js :
const mongoose = require('mongoose');
const validator = require('validator');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken')
const crypto = require('crypto')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please enter your name'],
maxLength: [30, 'Your name cannot exceed 30 characters']
},
email: {
type: String,
required: [true, 'Please enter your email'],
unique: true,
validate: [validator.isEmail, 'Please enter valid email address']
},
password: {
type: String,
required: [true, 'Please enter your password'],
minlength: [6, 'Your password must be longer than 6 characters'],
select: false
},
avatar: {
public_id: {
type: String,
required: true
},
url: {
type: String,
required: true
}
},
role: {
type: String,
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
},
resetPasswordToken: String,
resetPasswordExpire: Date
})
userSchema.methods.getJwtToken = function () {
return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_TIME
});
}
controllers/authController.js
exports.registerUser = catchAsyncErrors(async (req, res, next) => {
const result = await cloudinary.v2.uploader.upload(req.body.avatar, {
folder: 'avatars',
width: 150,
crop: "scale"
})
const { name, email, password } = req.body;
const user = await User.create({
name,
email,
password,
avatar: {
public_id: result.public_id,
url: result.secure_url
}
})
sendToken(user, 200, res)
})
and your utils/jwtToken.js
const sendToken = (user, statusCode, res) => {
// Create Jwt token
const token = user.getJwtToken();
// Options for cookie
const options = {
expires: new Date(
Date.now() + process.env.COOKIE_EXPIRES_TIME * 24 * 60 * 60 * 1000
),
httpOnly: true
}
res.status(statusCode).cookie('token', token, options).json({
success: true,
token,
user
})
}
module.exports = sendToken;
The error is due to your user to be undefined. You should handle that case, but if you don't want for any reason you can just check it is not null using req.user?.role:
exports.authorizeRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user?.role)) {
return next(
new ErrorHandler(`Role (${req.user?.role}) is not allowed to acccess this resource`, 403))
}
next()
}
}
In this case, if the user is not defined the role will be undefined too (returning the same error of Role (${req.user.role}) is not allowed to acccess this resource)
NULL CHECK USER
exports.authorizeRoles = (...roles) => {
if (!req.user) //throw error (user doesn't exist in db)
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(
new ErrorHandler(`Role (${req.user.role}) is not allowed to acccess this resource`, 403))
}
next()
}
}

Redirect from '/user/login' to '/user/me' in express with JWT and auth middleware

I'm trying to redirect a user to their profile after logging them in. What I'm trying to do is when they log in, we will find the user by credentials and then generate and auth token from them (note I created a user const for testing purposes). After both are done, I'll set a header Authorization, use the token, and pass it to the /user/me route. Here are my routes:
(login POST route, "/user/login"):
router.post('/user/login', async (req, res) => {
try {
const user = await User.findByCredentials(req.body.email, req.body.password)
const token = await user.generateAuthToken()
res.header('Authorization', 'Bearer '+token)
res.status(302).redirect('/user/me')
} catch (err) {
res.status(400).send(err)
}
})
(profile route: "/user/me"):
router.get('/user/me', auth, async (req, res) => {
res.send(req.user)
})
(the "auth" middleware that I'm passing in the previous method):
const auth = async (req, res, next) => {
try{
const token = req.header('Authorization').replace('Bearer ', '')
const decoded = jwt.verify(token, SECRET_TOKEN)
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token})
console.log(token)
if(!user) {
throw new Error("User not found")
}
req.token = token
req.user = user
next()
} catch(err) {
res.status(503).send({error: 'Please authenticate'})
}
}
But whenever I try this, it gives my 503 error from the auth method:
{
"error": "Please authenticate"
}
The Authorization header passes correctly as I've seen in my dev tools.
For more information, here's what the generateAuthToken & findByCredentials methods look like:
userSchema.methods.generateAuthToken = async function() {
const user = this
const token = jwt.sign({ _id: user._id.toString() }, SECRET_TOKEN)
user.tokens = user.tokens.concat({token})
await user.save()
return token
}
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({email})
if(!user) {
throw new Error({message: "Unable to log in."})
}
const isValid = await bcrypt.compare(password, user.password)
if(!isValid) {
throw new Error({message: "Unable to log in."})
}
return user
}
For more more information, here's what my User model looks like:
const userSchema = mongoose.Schema({
email:{
type: String,
required: true,
unique: true,
trim: true,
validate(value) {
if(!validator.isEmail(value)) {
throw new Error("Not a valid email")
}
}
},
password:{
type: String,
required: true,
validate(value) {
if(value === "password") {
throw new Error("Enter a strong password.")
}
if(value.length < 8) {
throw new Error("Enter minimum 8 letters for a password.")
}
}
},
tokens: [{
token:{
type: String,
required: true
}
}]
})
I've solved it by using cookies. It maybe a temporary work-around but I'll find resources to make it more secure!
In the login route:
res.cookie('Authorization', `Bearer ${token}`, {
maxAge: 60000,
})
In the auth middleware:
const token = req.cookies['Authorization'].replace('Bearer ', '')

POSTMAN hangs on sending request in an express/Nodejs application

I have a protected GET route to find and return a document from a MongoDb collection based on the logged in user id. I have created a middleware to just let the authenticated user access the route. When I test the route in postman it keeps sending request and never stops. It is definitely because of the authentication middleware, because when I disable the middleware the route works properly.
Here is my code for the route:
const getResearcherProfile = asyncHandler(async (req, res) => {
try {
const profile = await Researcher.findOne({ user: req.user._id })
res.json(profile)
} catch (error) {
res.status(500)
throw new Error('Server error')
}
})
The Researcher Model is as follow:
import mongoose from 'mongoose'
const researcherSchema = mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'users',
},
organization: {
type: String,
required: true,
},
researchFields: [
{
field: String,
},
],
},
{
timestamps: true,
}
)
const Researcher = mongoose.model('researcher', researcherSchema)
export default Researcher
And here is the authentication middleware:
const protect = () => asyncHandler(async (req, res, next) => {
let token
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
try {
token = req.headers.authorization.split(' ')[1]
// decode the token
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.user = await User.findById(decoded.id).select('-password')
next()
} catch (error) {
console.error(error)
res.status(401)
throw new Error('Not authorized, token failed')
}
}
if (!token) {
res.status(401)
throw new Error('Not authorized, no token')
}
})
Does anybody have an idea?

Trying to get current logged in user tasks in Nodejs and MongoDB

I'm working on a project that enables the admin to assign tasks to different users, every users should only see his own tasks.
I tried doing that by using the user.id as key, when the user logs in we send a token, and that token includes the user_id and other user info, I'm trying to extract the id from that token and view tasks based on that.
Tasks Model
const TaksSchema = new Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
},
taskName: {
name: String,
},
taskDesc: {
name: String,
},
dateAssigned: {
type: Date,
default: Date.now,
},
requiredDate: {
type: Date,
},
completed: { type: Boolean, default: false },
});
// Export Schema
module.exports = Tasks = mongoose.model('tasks', TaksSchema);
User model
const UserSchema = new Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
role: {
type: String,
enum: ['basic', 'admin'],
default: 'basic',
},
avatar: {
type: String,
},
date: {
type: Date,
default: Date.now,
},
});
// Export Schema
module.exports = User = mongoose.model('users', UserSchema);
tasks route
router.get('/', (req, res) => {
const errors = {};
Tasks.findOne({ user: req.user.id })
.populate('user', ['name', 'avatar'])
.then((task) => {
if (!task) {
errors.notask = "There's no Tasks Right Now";
return res.status(400).json(errors);
}
res.json(task).catch((err) => res.status(404).json(err));
});
});
When I try sending the get request from postman I get this error
TypeError: Cannot read property 'id' of undefined
For security I'm sending the Id through JWT token.
Here is the code
const payload = { id: user.id, name: user.name, avatar: user.avatar }; // Create jwt patload
// Sign the token
jwt.sign(
payload,
keys.secretOrKey,
{ expiresIn: 3600 },
(err, token) => {
res.json({ sucess: true, token: 'Bearer ' + token });
}
);
You must first verify/decode the ID in order to receive the payload.
In the code, you are trying to access the id field from user. Actually you need to add a middleware which validates the JWT and appends the result in the user field.
Example :
middlewares/validateJwt.js
Assuming you are sending JWT in the header as Bearer Token.
try{
let token = req.headers.authorization.split(" ")[1]; // Bearer <token>
let result = jwt.verify(token, "JWT_SECRET", options);
req.user = result;
next();
} catch...
Your API should look like
GET /api//users/:id
In that case you can use
req.params.id
as a first argument.
Pranesh A S's answer should get you going. To complete his suggestion, if you store the middleware validateJWT.js as
const jwt = require("jsonwebtoken");
module.exports = function (req, res, next) {
const token = req.header("x-auth-token");
if (!token) {
res.status(401).json({ msg: "NO token. Auth Failed" });
}
try {
console.log(token);
const decoded = jwt.verify(token, "Secret key");
req.user = decoded.user;
next();
} catch (err) {
console.log(err);
res.status(401).json({ msg: "Token is not valid" });
}
};
import it in your App.js file and while defining routes,
use
const validateJWT =require ("./middleware/validateJWT")
and
app.get("/", validateJWT, (req, res)=>{
// do stuff
})

Saving ref to a User login

I'm having a trouble to write a simple User and Tweet Schemas relation.
I created a middleware that will check a user token once he logged in. This is a token based authentication. The problem lies in the '/tweet' route section of how to save a tweet to a Logged in user.
schema.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = new Schema({
name: String,
username: { type: String, required: true, index: { unique: true }},
password: { type: String, required: true, select: false },
level: String,
tweet: [{
type: Schema.Types.ObjectId, ref: 'Tweet'
}],
});
var TweetSchema = new Schema({
_creator: { type: Number, ref: 'User'},
title: String,
content: String
});
module.exports = mongoose.model('User', UserSchema);
module.exports = mongoose.model('Tweet', TweetSchema);
api.js
let just assume that I have written '/login' route for log in a user and create a token above this middleware.
// The middleware for verifying user's token ( The user already login )
apiRouter.use(function(req, res, next) {
// do logging
console.log("Somebody just came to our app!");
var token = req.body.token || req.param('token') || req.headers['x-access-token'];
// check if token exist
if(token) {
jwt.verify(token, superSecret, function(err, decoded) {
if(err) {
res.status(403).send({ success: false, message: 'Failed to authenticate user' });
} else {
// if everything is good save request for use in other routes
req.decoded = decoded;
next();
}
});
} else {
res.status(403).send({ success: false, message: 'No token provided' });
}
});
// another route for creating a tweet, in order to use this route, user's token must be verified.
apiRouter.route('/tweet')
.post(function(req, res) {
// The problem is I dont know how to get a user that just Login to create a tweet that will save in his user_tweet Schema.
var tweet = new Tweet({
_creator: ???,
title: req.body.title,
content: req.body.content
});
tweet.save(function(err) {
if(err) res.send(err);
res.json({ message: "New tweet has been added" });
});
});
Currently I'm having a trouble to create a tweet and saves it to a User that already log in and token has been verified. What should I write in '/tweet' route section to achieve my goal.
I'm trying to emulate a Twitter app for you guys information.
I don't know what you're using to generate your tokens, but the standard practice is to encode the user id as part of the token. So when you decode the token, like you've done here:
req.decoded = decoded;
req.decoded it should contain the user id, which you can then use to look up the user and check their permissions, or in your case, you'd use it to create your tweet object.
So something like this:
// create a json webtoken
var token = jwt.sign({
id = user._id,
name: user.name,
username: user.username
}, superSecret, {
expiresInMinute: 1440
});
Then do:
var tweet = new Tweet({
_creator: req.decoded.id,
title: req.body.title,
content: req.body.content
});

Resources