Authentication and Authorization

Authentication and authorization are critical components of any web application. Authentication verifies the identity of a user, while authorization determines what an authenticated user is allowed to do. This chapter will cover these concepts in detail, using Node.js and Express.js to create a secure and robust authentication and authorization system. We'll go from the basics to advanced topics, including practical examples, code snippets, and detailed explanations.

Authentication and Authorization

Authentication is the process of verifying who a user is. Common methods include username and password, biometric verification, and multi-factor authentication (MFA).

Authorization determines what resources a user can access and what actions they can perform. It typically follows after authentication.

Setting Up the Environment

Initialize the project:

				
					mkdir auth-example
cd auth-example
npm init -y
npm install express mongoose body-parser jsonwebtoken bcryptjs

				
			

Project Structure:

				
					auth-example/
├── models/
│   └── user.js
├── routes/
│   ├── auth.js
│   └── users.js
├── middlewares/
│   └── auth.js
├── app.js
├── index.js
├── package.json

				
			

Authentication with JSON Web Tokens (JWT)

JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims between two parties. JWTs can be signed using a secret or a public/private key pair.

How JWT Works:

  • The client sends credentials (username and password) to the server.
  • The server verifies the credentials and generates a JWT.
  • The client stores the JWT and includes it in the Authorization header for subsequent requests.
  • The server validates the JWT and allows or denies access to protected routes.

Implementing Registration and Login

1.Create the User Model

				
					// models/user.js
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,
    enum: ['user', 'admin'],
    default: 'user'
  }
});

userSchema.pre('save', async function (next) {
  if (this.isModified('password')) {
    this.password = await bcrypt.hash(this.password, 10);
  }
  next();
});

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

				
			

2.Create Authentication Routes

				
					// routes/auth.js
const express = require('express');
const router = express.Router();
const User = require('../models/user');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

// Registration
router.post('/register', async (req, res) => {
  const { username, password } = req.body;
  try {
    const existingUser = await User.findOne({ username });
    if (existingUser) {
      return res.status(400).json({ message: 'Username already exists' });
    }

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

    const token = jwt.sign({ userId: user._id, role: user.role }, 'SECRET_KEY', { expiresIn: '1h' });
    res.status(201).json({ token });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// Login
router.post('/login', async (req, res) => {
  const { username, password } = req.body;
  try {
    const user = await User.findOne({ username });
    if (!user || !(await bcrypt.compare(password, user.password))) {
      return res.status(400).json({ message: 'Invalid username or password' });
    }

    const token = jwt.sign({ userId: user._id, role: user.role }, 'SECRET_KEY', { expiresIn: '1h' });
    res.status(200).json({ token });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

				
			

3.Protecting Routes with Middleware

				
					// middlewares/auth.js
const jwt = require('jsonwebtoken');

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  if (token == null) return res.status(401).json({ message: 'Access Denied' });

  jwt.verify(token, 'SECRET_KEY', (err, user) => {
    if (err) return res.status(403).json({ message: 'Invalid Token' });
    req.user = user;
    next();
  });
}

function authorizeRole(role) {
  return (req, res, next) => {
    if (req.user.role !== role) {
      return res.status(403).json({ message: 'Access Denied' });
    }
    next();
  };
}

module.exports = { authenticateToken, authorizeRole };

				
			

Authentication with JSON Web Tokens (JWT)

Role-Based Access Control (RBAC) restricts access to resources based on the user’s role.

Example of Protected Routes:

				
					// routes/users.js
const express = require('express');
const router = express.Router();
const { authenticateToken, authorizeRole } = require('../middlewares/auth');
const User = require('../models/user');

// Get all users (admin only)
router.get('/', authenticateToken, authorizeRole('admin'), async (req, res) => {
  try {
    const users = await User.find();
    res.json(users);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

				
			

Refresh Tokens and Token Expiration

JWTs can expire, requiring the client to obtain a new token without re-authenticating.

Implementing Refresh Tokens:

  1. Add a refresh token to the user schema:

				
					// models/user.js
const userSchema = new mongoose.Schema({
  // Other fields...
  refreshToken: String
});

				
			

2.Create a refresh token route:

				
					// routes/auth.js
router.post('/token', async (req, res) => {
  const { token } = req.body;
  if (!token) return res.status(401).json({ message: 'Access Denied' });

  try {
    const user = await User.findOne({ refreshToken: token });
    if (!user) return res.status(403).json({ message: 'Invalid Token' });

    jwt.verify(token, 'REFRESH_SECRET_KEY', (err, user) => {
      if (err) return res.status(403).json({ message: 'Invalid Token' });

      const accessToken = jwt.sign({ userId: user._id, role: user.role }, 'SECRET_KEY', { expiresIn: '1h' });
      res.json({ accessToken });
    });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

				
			

3.Generate refresh token on login and registration:

				
					// routes/auth.js
const refreshToken = jwt.sign({ userId: user._id, role: user.role }, 'REFRESH_SECRET_KEY');
user.refreshToken = refreshToken;
await user.save();
res.status(200).json({ accessToken: token, refreshToken });

				
			

Securing Passwords with Hashing

Using bcrypt to hash passwords before storing them in the database.

				
					// models/user.js
userSchema.pre('save', async function (next) {
  if (this.isModified('password')) {
    this.password = await bcrypt.hash(this.password, 10);
  }
  next();
});

				
			

Advanced Topics

1. Two-Factor Authentication (2FA): 2FA adds an additional layer of security. Popular methods include SMS, email, or authentication apps.

2. OAuth2: OAuth2 is a protocol for authorization. It allows third-party applications to access user resources without sharing credentials.

3. Password Reset: Implement a secure password reset mechanism using email tokens.

4. Rate Limiting: Prevent brute-force attacks by limiting the number of login attempts.

5. Secure Cookie Storage: Store tokens in secure, HTTP-only cookies to prevent XSS attacks.

Full Practical Example

Below is the complete code for a practical example of implementing authentication and authorization in a Node.js application.

Directory Structure:z

				
					auth-example/
├── models/
│   └── user.js
├── routes/
│   ├── auth.js
│   └── users.js
├── middlewares/
│   └── auth.js
├── app.js
├── index.js
├── package.json

				
			

index.js

				
					const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const authRouter = require('./routes/auth');
const usersRouter = require('./routes/users');
const app = express();
const port = 3000;

mongoose.connect('mongodb://localhost/auth-example', { useNewUrlParser: true, useUnifiedTopology: true });

app.use(bodyParser.json());

app.use('/auth', authRouter);
app.use('/users', usersRouter);

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

				
			

models/user.js

				
					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,
    enum: ['user', 'admin'],
    default: 'user'
  },
  refreshToken: String
});

userSchema.pre('save', async function (next) {
  if (this.isModified('password')) {
    this.password = await bcrypt.hash(this.password, 10);
  }
  next();
});

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

				
			

routes/auth.js

				
					const express = require('express');
const router = express.Router();
const User = require('../models/user');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

// Registration
router.post('/register', async (req, res) => {
  const { username, password } = req.body;
  try {
    const existingUser = await User.findOne({ username });
    if (existingUser) {
      return res.status(400).json({ message: 'Username already exists' });
    }

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

    const accessToken = jwt.sign({ userId: user._id, role: user.role }, 'SECRET_KEY', { expiresIn: '1h' });
    const refreshToken = jwt.sign({ userId: user._id, role: user.role }, 'REFRESH_SECRET_KEY');
    user.refreshToken = refreshToken;
    await user.save();

    res.status(201).json({ accessToken, refreshToken });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// Login
router.post('/login', async (req, res) => {
  const { username, password } = req.body;
  try {
    const user = await User.findOne({ username });
    if (!user || !(await bcrypt.compare(password, user.password))) {
      return res.status(400).json({ message: 'Invalid username or password' });
    }

    const accessToken = jwt.sign({ userId: user._id, role: user.role }, 'SECRET_KEY', { expiresIn: '1h' });
    const refreshToken = jwt.sign({ userId: user._id, role: user.role }, 'REFRESH_SECRET_KEY');
    user.refreshToken = refreshToken;
    await user.save();

    res.status(200).json({ accessToken, refreshToken });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// Refresh Token
router.post('/token', async (req, res) => {
  const { token } = req.body;
  if (!token) return res.status(401).json({ message: 'Access Denied' });

  try {
    const user = await User.findOne({ refreshToken: token });
    if (!user) return res.status(403).json({ message: 'Invalid Token' });

    jwt.verify(token, 'REFRESH_SECRET_KEY', (err, user) => {
      if (err) return res.status(403).json({ message: 'Invalid Token' });

      const accessToken = jwt.sign({ userId: user._id, role: user.role }, 'SECRET_KEY', { expiresIn: '1h' });
      res.json({ accessToken });
    });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

				
			

routes/users.js

				
					const express = require('express');
const router = express.Router();
const { authenticateToken, authorizeRole } = require('../middlewares/auth');
const User = require('../models/user');

// Get all users (admin only)
router.get('/', authenticateToken, authorizeRole('admin'), async (req, res) => {
  try {
    const users = await User.find();
    res.json(users);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

				
			

middlewares/auth.js

				
					const jwt = require('jsonwebtoken');

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  if (token == null) return res.status(401).json({ message: 'Access Denied' });

  jwt.verify(token, 'SECRET_KEY', (err, user) => {
    if (err) return res.status(403).json({ message: 'Invalid Token' });
    req.user = user;
    next();
  });
}

function authorizeRole(role) {
  return (req, res, next) => {
    if (req.user.role !== role) {
      return res.status(403).json({ message: 'Access Denied' });
    }
    next();
  };
}

module.exports = { authenticateToken, authorizeRole };

				
			

In this chapter, we've covered the essential concepts of authentication and authorization in Node.js, from basic JWT-based authentication to advanced topics like role-based access control and refresh tokens. By following the principles and best practices outlined here, you can create secure and robust authentication systems for your Node.js applications. This comprehensive guide should provide all the information you need to implement authentication and authorization in your projects.Happy coding !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India