Authentication and Authorization in Node JS
User authentication & authorization is one of the important part of any web application. There are several kinds of way to handle authentication, we can rely on third party service like Passport. But in this article we will use very simple & self developed approach, which will help us to understand core part of authentication.
Technology Used
- Database: MySQL
- Password Hash: bcryptjs
- Token: JWT
Basic Structure
To create basic structure for different kind of endpoints like registration or login we will use express as router. And we will create a folder for routers.
In routes folder we will create a file auth.js for authentication related routes.
routes/auth.js
const router = require('express').Router();
const userController = require('../controllers/user.controller');
// Register a new User
router.post('/register', userController.register);
// Login
router.post('/login', userController.login);
module.exports = router;
index.js
const express = require('express');
const app = express();
// Import Routes
const authRoute = require('./routes/auth');
// Route Middlewares
app.use('/api/users', authRoute);
app.listen(3005, () => console.log('Server is running'));
Registration / SignUp
We have already defined a route for register. Now we will create a controllers folder for controllers & models folder for models. This structures are not required, you may have different structure or you can do everything in a single js file.
So in controller register part should be look like this
controllers/user.controller.js
// Register a new User
exports.register = async (req, res) => {
//Hash password
const salt = await bcrypt.genSalt(10);
const hasPassword = await bcrypt.hash(req.body.password, salt);
// Create an user object
const user = new User({
mobile: req.body.mobile,
email: req.body.email,
name: req.body.name,
password: hasPassword,
status: req.body.status || 1
});
// Save User in the database
try {
const id = await User.create(user);
user.id = id;
delete user.password;
res.send(user);
}
catch (err){
res.status(500).send(err);
}
};
Here we have used bcryptjs package to hash the password. And used mysql as database.
models/user.model.js
User.create = async (newUser) => {
let insert = await sql.query("INSERT INTO user SET ?", newUser);
if( insert.insertId ) {
return insert.insertId;
}
else {
return;
}
};
Login
Here we have to check user email or mobile against the provided password. If provided info is correct, then we have to generate a token & return to the client. So initially we will check for email or mobile in our database, if user exist, then will call bcrypt.compare()
, function of bcryptjs. Then we will call jsonwebtoken for token & send back to client.
Now we will see implementation.
controllers/user.controller.js
// Login
exports.login = async (req, res) => {
try {
// Check user exist
const user = await User.login(req.body.mobile_or_email);
if (user) {
const validPass = await bcrypt.compare(req.body.password, user.password);
if (!validPass) return res.status(400).send("Mobile/Email or Password is wrong");
// Create and assign token
const token = jwt.sign({id: user.id, user_type_id: user.user_type_id}, config.TOKEN_SECRET);
res.header("auth-token", token).send({"token": token});
// res.send("Logged IN");
}
}
catch (err) {
if( err instanceof NotFoundError ) {
res.status(401).send(`Mobile/Email or Password is wrong`);
}
else {
let error_data = {
entity: 'User',
model_obj: {param: req.params, body: req.body},
error_obj: err,
error_msg: err.message
};
res.status(500).send("Error retrieving User");
}
}
};
models/user.model.js
User.login = async (value) => {
let row = await sql.query(`SELECT * FROM user WHERE mobile = ? OR email = ?`, [value, value]);
if( row.length ) {
return row[0];
}
else {
throw new NotFoundError("User does not exist");
}
};
Authentication & Authorization
We often used interchangeably, authentication and authorization, but those words represent fundamentally different functions.
In simple terms, authentication is the process of verifying who a user is, while authorization is the process of verifying what they have access to.
Initially we will just check token in the header of request for restricted routes, then allow or deny request. Then we will check logged in user's permitted routes to access.
To keep it simple, we are keeping a field user_type_id & considering the value
1 = Admin
2 = Customer/Normal user.
Now creating a middle-ware to do this job.
helpers/auth.middleware.js
const config = require("../config/config");
const jwt = require("jsonwebtoken");
exports.loggedIn = function (req, res, next) {
let token = req.header('Authorization');
if (!token) return res.status(401).send("Access Denied");
try {
if (token.startsWith('Bearer ')) {
// Remove Bearer from string
token = token.slice(7, token.length).trimLeft();
}
const verified = jwt.verify(token, config.TOKEN_SECRET);
if( verified.user_type_id === 2 ){ // Check authorization, 2 = Customer, 1 = Admin
let req_url = req.baseUrl+req.route.path;
if(req_url.includes("users/:id") && parseInt(req.params.id) !== verified.id){
return res.status(401).send("Unauthorized!");
}
}
req.user = verified;
next();
}
catch (err) {
res.status(400).send("Invalid Token");
}
}
exports.adminOnly = async function (req, res, next) {
if( req.user.user_type_id === 2 ){
return res.status(401).send("Access Denied");
}
next();
}
And how to correctly add the html page with registration here, please show
Really helpful. Thank you very much!
This article will help you grasp different concepts behind Node.js and will empower you to create production ready applications. This article expects the reader to know Babel and how to set it up.