JWT Auth Token Error - Can't create chatroom, JWT malformed - javascript

I'm building login system for my ChatApp and got stuck on JWT malformed when trying to create a new chatroom. When I login I get token and I try to send request from Postman to create chatroom, I put Token as bearer and in body I set name as my chatroom which is ok to put. Code:
auth.js:
const jwt = require('jwt-then')
module.exports = async (req, res, next) => {
try {
if (!req.headers.authorization) throw 'Forbidden!'
const token = req.headers.authorization.split('')[1]
const payload = await jwt.verify(token, process.env.SECRET)
req.payload = payload
next()
} catch (err){
res.status(401).json({
message: err.message
})
}
}
routes/chatroom.js:
const { catchErrors } = require('../handlers/errorHandlers.js')
const chatroomController = require('../controllers/userControllers.js')
const auth = require('../middlewares/auth')
router.post('/', auth, catchErrors(chatroomController.createChatroom))
module.exports = router
chatroomController:
const Chatroom = mongoose.model('Chatroom')
exports.createChatroom = async (req, res) => {
const {name} = req.body
const nameRegex = /^[A-Za-z\s]+$/
if (!nameRegex.test(name)) throw 'Chatroom name can only contain alphabets.'
const chatroomExists = await Chatroom.findOne({name})
if (chatroomExists) throw 'Chatroom already exists.'
const chatroom = new Chatroom({
name,
})
await chatroom.save()
res.json({
message: 'Chatroom created!',
})
}
Please help me

Related

Is it possible to decrypt a password without having to use compare? (bcrypt)

I'm implementing a "forgot password" system in my authentication API in node. For this I created a "forgetPassword" controller where I send the user's email through req.body and with that an email with the recovery token is sent to the user, and that same token is also sent inside of the model user in the database in "tokenReset". The second controller is "resetPassword", where this recovery token is sent by params and a new password is sent in the req.body.
The biggest problem is, I think it's not safe for this token to be in the database without being encrypted, as someone inside the database can take advantage of this as a flaw, so I encrypted the token before sending it to the database. But the big question is, I have to fetch this token inside the database in "resetPassword" controller through the token sent in the params, but the token inside my database is encrypted and the one in the params is not. What would be the best way to resolve this?
forgotPassword controller
const jwt = require('jsonwebtoken');
const db = require('../../models/index');
const sendEmail = require('../../utils/mailer');
const bcrypt = require('bcryptjs');
exports.store = async (req, res) => {
const { email } = req.body;
const secret = process.env.JWT_SECRET;
try {
const user = await db.User.findOne({ where: { email } });
if (!user) {
res.status(400).json('User does not exist.');
}
const token = jwt.sign({ id: user.id }, secret, {
expiresIn: process.env.EXPIRES_FORGOTPASS,
});
const hashedToken = bcrypt.hashSync(token, 10);
await user.update({
tokenReset: hashedToken,
});
sendEmail({
from: 'noreply#email.com',
to: 'admin#gmail.com',
subject: 'Reset your password',
text: `Token sending email to reset password account from ${user.email}`,
html: `<h2>Token sending email to reset password account from ${user.email}</h2>
<h4>${token}</h4>
`,
});
return res.status(200).json('Check the verification link in your email!');
} catch (err) {
return console.log(err);
}
}
resetPassword controller
const jwt = require('jsonwebtoken');
const db = require('../../models/index');
const bcrypt = require('bcryptjs');
const sendEmail = require('../../utils/mailer');
exports.store = async (req, res) => {
const { token } = req.params;
const { password } = req.body;
const secret = process.env.JWT_SECRET;
try {
const userExists = await db.User.findOne({ where: { tokenReset: token } });
if (!userExists) {
return res.status(401).json('User not found in our database!');
}
try {
await jwt.verify(token, secret);
} catch (err) {
return res.status(401).json('Link expired, make a new password reset request!');
}
const hashedNewPass = bcrypt.hashSync(password, 10);
await userExists.update({
password: hashedNewPass,
tokenReset: "",
});
return res.status(200).json(`Password updated!`);
} catch (err) {
return console.log(err);
}
}

PassportJS JWT: protected-route is Unauthorized in my browser , but Authorized on Postman

I'm trying to implement JWT authentication, and I can't figure out why in Postman protected-route is available, while in my browser protected-route is Unauthorized.
I'm a beginner and its first time implementing jwt authentication in my project following this tutorial https://www.youtube.com/playlist?list=PLYQSCk-qyTW2ewJ05f_GKHtTIzjynDgjK
Problem
/login and /register work fine and I can see from the log they issue a JWT token in the req.headers
like 'Authorization':'Bearer <token>', however when I try to access GET /protected-route in my browser, it returns Unauthorized, and I can see no logging from JWTStrategy.
I think req.headers.Authorization is not set to all HTTP requests in the app, but only to POST /login, /register routes So my questions are:
Is this req.headers["Authorization"] = 'Bearer <toekn>'; correct way to set req headers to all GET and POST request in the app?
Does the jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken() checks for Authorization property in req.headers or res.headers?
I have provided the relevant code snippets please have a look!
Code:
app.js
//...
require('./config/database'); // require the db
// ...
const passport = require('passport');
const strategy = require('./config/passport').strategy;
// ...
app.use(passport.initialize());
passport.use(strategy);
// ...
module.exports = app
router/index.js
const router = express.Router();
//...
const User = require('../config/database').User;
const passport = require('passport');
const utils = require('../lib/utils');
// register route
router.post('/register', function(req, res, next){
const saltHash = utils.genPassword(req.body.password);
console.log("req.body.password is: " + req.body.password)
const salt = saltHash.salt;
const hash = saltHash.hash;
const newUser = new User({
username: req.body.username,
hash: hash,
salt: salt
})
newUser.save()
.then((user) => {
const jwt = utils.issueJWT(user);
if (jwt){
req.headers["Authorization"] = jwt.token;
}
res.redirect('/login')
})
.catch(err => next(err))
});
// login route
router.post('/login', function(req, res, next){
User.findOne({username: req.body.username})
.then((user) => {
if(!user) {
res.status(401).json({success: false, msg: "Could not find user "})
}
// validate the user
const isValid = utils.validPassword(req.body.password, user.hash, user.salt)
if(isValid) {
// issue a JWT
const jwt = utils.issueJWT(user);
if (jwt){
req.headers["Authorization"] = jwt.token;
}
res.redirect("/")
} else {
res.status(401).json({success: false, msg: "you entered the wrong password"})
}
})
.catch(err => next(err))
});
// GET protected route
router.get("/protectd-route", passport.authenticate('jwt', {session: false}), async (req, res, next) => {
res.render("user/getuser.pug")
next()
})
passport.js
const User = require('../config/database').User;
// ...
const Strategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
/// PUB_KEY
const pathToKey = path.join(__dirname, '..', 'id_rsa_pub.pem');
const PUB_KEY = fs.readFileSync(pathToKey, 'utf8');
// options
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: PUB_KEY,
algorithms: ['RS256']
};
// strategy
const strategy = new Strategy(options, (payload, done) => {
User.findOne({_id: payload.sub})
.then((user) => {
if(user) {
return done(null, user)
} else {
return done(null, false)
}
})
.catch((err) => done(err, null))
});
module.exports.strategy = strategy;
utils.js
genPassword() - Creating a salt and hash out of it to store in db
validPassword() - re-hashing user salt and hash to verify
issueJWT() - signing user with jsonwebtoken
const crypto = require('crypto');
const jsonwebtoken = require('jsonwebtoken');
const User = require('../config/database').User;
//...
const pathToKey = path.join(__dirname, '..', 'id_rsa_priv.pem');
const PRIV_KEY = fs.readFileSync(pathToKey, 'utf8');
// validate in /login
function validPassword(password, hash, salt) {
var hashVerify = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex');
return hash === hashVerify;
}
// generate in /register
function genPassword(password) {
var salt = crypto.randomBytes(32).toString('hex');
var genHash = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex');
return {
salt: salt,
hash: genHash
};
}
// sign
function issueJWT(user) {
const _id = user._id;
const expiresIn = '86400';
const payload = {
sub: _id,
iat: Date.now()
};
const signedToken = jsonwebtoken.sign(payload, PRIV_KEY, { expiresIn: expiresIn, algorithm: 'RS256' });
return {
token: "Bearer " + signedToken,
expires: expiresIn
}
}
module.exports.validPassword = validPassword;
module.exports.genPassword = genPassword;
module.exports.issueJWT = issueJWT;
Postman
In Postman, the protected route is showing successfully, with Headers set as above.
The browser network is showing this, there is no Authorization property both in response and request headers
Clearly, I'm missing something, but I can't figure it out. Any help is appreciated
here is also my db
database.js
const uri = process.env.MONGO_URI
const connection = mongoose.connection;
mongoose.connect(uri, {
useUnifiedTopology: true,
serverSelectionTimeoutMS: 5000
})
connection.on('error', console.error.bind(console, "connection error"))
connection.once('open', () => {
console.log('MongoDB database connection has been established successfully')
})
const userSchema = new mongoose.Schema({
username: String,
hash: String,
salt: String
});
const User = mongoose.model('User', userSchema )
module.exports.User = User;

CRUD Node JS (Cannot Post)

I'm currently trying my hand at "basic authentication" for users. I can't now get a http response back to my query. The path of the request is 'AuthenticationService' --> 'UserService'. But from 'UserService' I can't get to 'UserRoute' and therefore not to 'httpServer'. The correct order should be: AuthenticationService -->UserService --->Userroute-->httpServer If I change in AuthenticationRoute.js router.post('/', authenticate); to router.post('/authenticate', authenticate) I get a http response, but I dont transmit any data....What do I have to do ?
UserService.js
async function authenticate({ username, password }) {
const user= User.find({userName:username} && {password:password})
/* const user = User.find(u => u.userName === so && u.password === password); */
if (user) {
const { password, ...userWithoutPassword } = user;
return userWithoutPassword;
}
}
module.exports = {
authenticate
}
UserRoute.js
var userService = require('./UserService')
router.post('/', authenticate);
function authenticate(req, res, next) {
userService.authenticate(req.body)
.then(user => user ? res.json(user) : res.status(400).json({ message: 'Username or password is incorrect' }))
.catch(err => next(err));
}
module.exports = router;
AuthenticationService.js
async function basicAuth(req, res, next) {
// make authenticate path public
if (req.path === '/') {
return next();
}
if (!req.headers.authorization || req.headers.authorization.indexOf('Basic ') === -1) {
return res.status(401).json({ message: 'Missing Authorization Header' });
}
// verify auth credentials
const base64Credentials = req.headers.authorization.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
const [username, password] = credentials.split(':');
console.log(username+password);
const user = await userService.authenticate({ username, password });
if (!user) {
return res.status(401).json({ message: 'Invalid Authentication Credentials' });
}
req.user=user
next();
}
module.exports = {
basicAuth
}
AuthenticationRoute.js
var express = require('express');
var router = express.Router();
var authenticationService=require('./AuthenticationService')
router.post('/authenticate', authenticationService.basicAuth);
module.exports=router;
httpServer.js
const userRoutes = require('./endpoints/user/UserRoute')
const authenticationRoutes= require('./endpoints/authentication/AuthenticationRoute')
var axios = require('axios');
app.use(authenticationRoutes);
app.use(userRoutes);
The request I try to send is:
POST http://localhost:8080/authenticate
Authorization: Basic YWRtaW46MTIz

How to create user group security for REST API using middleware

In order to secure REST API I'm using middleware to check for user's JWT token and only allow that particular user to access his own data.
In auth.js
const jwt = require('jsonwebtoken')
const User = require('../models/user')
const auth = async (req, res, next) => {
try {
const token = req.header('Authorization').replace('Bearer ', '')
const decoded = jwt.verify(token, process.env.JWT_SECRET)
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })
if (!user) { // If no user is found
throw new Error()
}
// if there's a user
req.token = token
req.user = user
next()
} catch (e) {
res.status(401).send({ error: 'Please authenticate.' })
}
}
module.exports = auth
In one of the get/update router
router.get('/itemShipmentStatus', auth, async (req, res) => {
// Get the items shipment status from db.
})
However, I've noticed I need to create a new admin user (e.g. admin 1, admin2) to get and update the itemShipmentStatus for all the users. Is there a way to achieve user group authentication through the middleware (auth.js?)
Update:
The only solution I can think of is to add another "userGroup" field to the user document when creating a new user. Then in the middleware auth.js add in another condition to check if the user belongs to the admin group.
if (!user || user.userGroup !== 'Admin') { // If no user is found
throw new Error()
}
Is this the conventional way of doing it?
I would suggest adding permissions array stored in the user. That way you are more flexible. Then
const auth = (allowedPermission) => (async (req, res, next) => {
try {
const token = req.header('Authorization').replace('Bearer ', '')
const decoded = jwt.verify(token, process.env.JWT_SECRET)
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })
if (!user) { // If no user is found
throw new Error()
}
if (!user.permissions.includes(allowedPermission)){
throw new Error() // Forbidden 403
}
// if there's a user
req.token = token
req.user = user
next()
} catch (e) {
res.status(401).send({ error: 'Please authenticate.' })
}
})
and in the route
router.get('/itemShipmentStatus', auth([admin, user]), async (req, res) => {
// Get the items shipment status from db.
})
Then it would be a matter to identify the correct code to run.
I would suggest considering the division of the api. A public api and an admin api. This is because conceptually a user may want to be admin and access its own itemShipmentStatus. So having
router.get('/itemShipmentStatus', auth([admin, user]), async (req, res) => {
// Get the items shipment status from db.
})
router.get('/admin/itemShipmentStatus', auth([admin]), async (req, res) => {
// Get the items shipment status from db of all user.
})
This allows an admin user to test the API as a normal user and get all the status as an admin.
A more conventional way of doing this would be to create an AuthRouter which extends the default express.Router and checks for allowed roles, so there will be no need to use middleware for each route.
Extending express.Router to check for roles:
const express = require('express');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
export default class AuthRouter extends express.Router {
addRoles(...roles) {
return this.use(checkAccessForRoles(...roles));
}
}
const checkAccessForRoles = (...roles) => async (req, res, next) => {
const token = req.header('Authorization').replace('Bearer ', '');
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token });
if (!roles.includes(user.role)) throw new Error('Forbidden');
req.user = user;
return next();
};
Using AuthRouter for ADMIN user role:
const adminRouter = new AuthRouter({
prefix: '/admin',
})
.addRoles(['ADMIN']);
adminRouter.get('/itemShipmentStatus', async (req, res) => {
// Get the items shipment status from db.
});

Big problem with security (JWT NodeJS), one token for all acces

I have a really big problem with security in my web application.
I implemented JWT token when user login to my application (REST API returns token).
In my jwt token, I have only userID. Problem is that, when I would like to login on user with ID = 1,
I can see and execute rest actions from all other users with the same token. for example:
When I looged userId = 1, I doing GET action: /api/users/1 and I have a information about user 1. But I can doing action /api/users/2, 3 etc.
All with one token. how to secure it?
const jwt = require('jsonwebtoken');
const env = require('../config/env.config.js');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, env.SECRET_KEY);
req.userData = decoded;
next();
} catch (error) {
return res.status(401).json({
message: 'Auth failed',
});
}
};
I think the best solution would be to create middleware that check the id of the sender and attach it to routes, similar to bellow
const middleware = (req, res, next) => {
const id = req.params.id || req.body.id || req.query.id
if (req.userData.id === id) {
next()
} else {
res.status(403).send({message: "forbidden"})
}
}
router.get("/api/users/:id", middleware, (req, res) => {
// do your staff
res.send({message: "ok"})
})
router.put("/api/users/:id", middleware, (req, res) => {
// do your staff
res.send({message: "ok"})
})

Categories