Simple Authentication and Authorization in Node.js with JWT: A Teacher-Student Example
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.