JWT Authentication and Authorization in Nodejs

JWT Authentication and Authorization in Nodejs: Implementing authentication and authorization in a Nodejs application is crucial for securing your web app.

In this blog post, we’ll walk through the process of setting up user authentication using JSON Web Tokens (JWT) and implementing role-based authorization.

Easy Steps to Implement

Before we start, make sure you have Node.js and npm installed. You can check this by running the following commands:

node -v
npm -v

Step 1: Initialize Your Project

First, create a new directory for your project and initialize it with npm:

mkdir auth-demo
cd auth-demo
npm init -y

Read Also : Implementing Code Coverage in Node js: A Comprehensive Guide Learn in 10 min

Step 2: Install Dependencies

We’ll need several npm packages for this project:

  • express for the web server.
  • bcryptjs for hashing passwords.
  • jsonwebtoken for creating and verifying JWTs.
  • mongoose for interacting with MongoDB.
  • dotenv for managing environment variables.

Install these packages by running:

npm install express bcryptjs jsonwebtoken mongoose dotenv

Step 3: Setup Your Project Structure

Create the following files and directories to organize your project:

auth-demo/

├── config/
│   └── db.js
├── controllers/
│   └── authController.js
├── middleware/
│   └── authMiddleware.js
├── models/
│   └── User.js
├── routes/
│   └── authRoutes.js
├── .env
├── app.js
└── package.json

Step 4: Configure Environment Variables

Create a .env file to store your environment variables:

MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_jwt_secret

Read Also: Securing Your Node.js API with Encryption and Sending Dynamic IV to Client : AES-CBC

Step 5: Setup MongoDB Connection

In config/db.js, setup your MongoDB connection:

const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    console.log('MongoDB connected');
  } catch (err) {
    console.error(err.message);
    process.exit(1);
  }
};

module.exports = connectDB;

Step 6: Define the User Model

In models/User.js, create a User schema:

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const UserSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  role: { type: String, required: true, enum: ['user', 'admin'], default: 'user' },
});

UserSchema.pre('save', async function (next) {
  if (!this.isModified('password')) {
    return next();
  }
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

UserSchema.methods.matchPassword = async function (password) {
  return await bcrypt.compare(password, this.password);
};

module.exports = mongoose.model('User', UserSchema);

Step 7: Create Authentication Controller

In controllers/authController.js, define your authentication logic:

const User = require('../models/User');
const jwt = require('jsonwebtoken');

const register = async (req, res) => {
  const { username, password } = req.body;

  try {
    let user = await User.findOne({ username });
    if (user) {
      return res.status(400).json({ msg: 'User already exists' });
    }

    user = new User({ username, password });
    await user.save();

    const payload = { user: { id: user.id, role: user.role } };
    const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' });

    res.status(201).json({ token });
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};

const login = async (req, res) => {
  const { username, password } = req.body;

  try {
    const user = await User.findOne({ username });
    if (!user) {
      return res.status(400).json({ msg: 'Invalid credentials' });
    }

    const isMatch = await user.matchPassword(password);
    if (!isMatch) {
      return res.status(400).json({ msg: 'Invalid credentials' });
    }

    const payload = { user: { id: user.id, role: user.role } };
    const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' });

    res.json({ token });
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};

module.exports = { register, login };

Read Also : Implementing Rate Limiting in Node APIs: Examples and Best Practices

Step 8: Create Authentication Middleware

In middleware/authMiddleware.js, create middleware to protect routes:

const jwt = require('jsonwebtoken');

const auth = (req, res, next) => {
  const token = req.header('x-auth-token');
  if (!token) {
    return res.status(401).json({ msg: 'No token, authorization denied' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded.user;
    next();
  } catch (err) {
    res.status(401).json({ msg: 'Token is not valid' });
  }
};

const authorize = (roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ msg: 'Forbidden' });
    }
    next();
  };
};

module.exports = { auth, authorize };

Step 9: Create Routes

In routes/authRoutes.js, define your routes:

const express = require('express');
const router = express.Router();
const { register, login } = require('../controllers/authController');
const { auth, authorize } = require('../middleware/authMiddleware');

router.post('/register', register);
router.post('/login', login);

// Example of a protected route
router.get('/admin', auth, authorize(['admin']), (req, res) => {
  res.send('Admin content');
});

module.exports = router;

Step 10: Setup Express Server

In app.js, setup your Express server:

const express = require('express');
const connectDB = require('./config/db');
const authRoutes = require('./routes/authRoutes');
require('dotenv').config();

const app = express();

// Connect to database
connectDB();

// Middleware
app.use(express.json());

// Routes
app.use('/api/auth', authRoutes);

// Start server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Step 11: Test Your API

Start your server:

node app.js

Use a tool like Postman to test your API endpoints:

  • Register a new user: POST /api/auth/register with username and password in the body.
  • Login: POST /api/auth/login with username and password in the body.
  • Access a protected route: GET /api/auth/admin with the x-auth-token header containing the JWT without Bearer.

Conclusion : JWT Authentication and Authorization in Nodejs

In this tutorial, we implemented a basic JWT authentication and authorization in Nodejs. This setup provides a solid foundation for securing your web application.

We can expand on this by adding more features such as password reset, email verification, and more detailed role-based access controls.