Simple Authentication and Authorization in Node.js with JWT: A Teacher-Student Example

·

3 min read

Project Structure

Create a folder structure for our Node.js project:

codeteacher-student-example-simple
├── modules
│   └── user.js
├── routes
│   ├── authRoutes.js
│   └── studentRoutes.js
├── .env
├── app.js
└── package.json

Step-by-Step Implementation

1. Setting Up Dependencies

Initialize a new Node.js project and install necessary packages:

mkdir teacher-student-example-simple
cd teacher-student-example-simple
npm init -y
npm install express bcryptjs dotenv jsonwebtoken mongoose

2. Creating the User Module

Create a user.js module to define the User schema using Mongoose:

// modules/user.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const userSchema = new Schema({
  username: { type: String, required: true },
  password: { type: String, required: true },
  role: { type: String, enum: ['teacher', 'student'], default: 'student' }
});

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

3. Implementing Authentication

Create authRoutes.js in the routes folder to handle authentication using basic username/password validation and JSON Web Tokens (JWT):

// routes/authRoutes.js

const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../modules/user');
const { JWT_SECRET } = process.env;

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

  try {
    // Check if user exists
    const user = await User.findOne({ username });

    if (!user) {
      return res.status(404).json({ message: 'User not found.' });
    }

    // Validate password
    const isMatch = await bcrypt.compare(password, user.password);

    if (!isMatch) {
      return res.status(401).json({ message: 'Invalid credentials.' });
    }

    // Generate JWT token
    const token = jwt.sign({ id: user._id, username: user.username, role: user.role }, JWT_SECRET);
    res.json({ token });

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

module.exports = router;

4. Creating Middleware for Authorization

Define middleware functions in studentRoutes.js for protecting routes based on user roles:

// routes/studentRoutes.js

const express = require('express');
const router = express.Router();
const { isAdmin } = require('../modules/middleware');

router.get('/students', isAdmin, (req, res) => {
  // Example route accessible only to teachers (admin)
  res.send('List of students');
});

module.exports = router;
// modules/middleware.js

const isAdmin = (req, res, next) => {
  const userRole = req.user.role;
  if (userRole === 'teacher') {
    next();
  } else {
    res.status(403).json({ message: 'Unauthorized access.' });
  }
};

module.exports = { isAdmin };

5. Configuring the Server

Set up app.js to configure Express server, connect to MongoDB using Mongoose, and define routes:

// app.js

const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const authRoutes = require('./routes/authRoutes');
const studentRoutes = require('./routes/studentRoutes');

dotenv.config();
const app = express();

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

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

// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('MongoDB connected'))
  .catch(err => console.error(err));

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

6. Environment Variables

Create a .env file in the root directory for sensitive information:

makefileCopy codePORT=3000
MONGODB_URI=mongodb://localhost:27017/teacher-student-example-simple
JWT_SECRET=your_jwt_secret_key

Explanation

  • User Module (user.js): Defines the MongoDB schema for users with roles (teacher or student).

  • Authentication Routes (authRoutes.js): Handles user login with basic username/password validation using bcrypt and generates a JWT token.

  • Authorization Middleware (middleware.js): Defines middleware function (isAdmin) for role-based access control (RBAC), allowing access only to users with the 'teacher' role.

  • Student Routes (studentRoutes.js): Example route accessible only to teachers (admin), demonstrating RBAC.

  • Server Configuration (app.js): Sets up an Express server, connects to MongoDB using Mongoose, and defines routes.

Conclusion

This simplified example demonstrates the basic principles of authentication and authorization in a Node.js application without using Passport.js. It focuses on using JSON Web Tokens (JWT) for authentication and role-based access control (RBAC) for authorization. This approach is easier to understand and implement for simpler use cases where Passport.js might be considered overkill.

You can expand upon this example by adding more features such as user registration, logout functionality, error handling, or more complex authorization rules to fit your specific project requirements.