Cách triển khai kiểm soát quyền truy cập theo vai trò trong API Express.js REST bằng Passport.js và JWT

Kiểm soát truy cập theo vai trò là cơ chế xác thực an toàn. Bạn có thể dùng nó để hạn chế quyền truy cập tới tài nguyên nào đó.

Kiểm soát quyền truy cập theo vai trò

Kiểu xác thực này giúp quản trị viên hệ thống kiểm soát quyền truy cập theo vai trò người dùng. Mức độ kiểm soát chi tiết này thêm lớp bảo mật, cho phép ứng dụng chặn truy cập bất hợp pháp.

Triển khai quyền truy cập theo vai trò bằng Passport.js và JWT

Bạn có hai cách để thực hiện việc này bao gồm dùng thư viện chuyên dụng như AcessControl hoặc tận dụng thư viện xác thực hiện tại để thực hiện cơ chế này.

Kiểm soát truy cập

Ở đây, JSON Web Token (JWT) cung cấp cách an toàn để chuyển thông tin xác thực, còn Passport.js đơn giản hóa quá trình xác thực bằng cách cung cấp middleware xác thực linh hoạt.

Dùng phương pháp này, bạn có thể phân bổ vai trò người dùng, mã hóa chúng trong JWT khi họ xác thực. Sau đó, bạn có thể dùng JWT để xác minh danh tính & vai trò của người dùng trong các truy vấn tiếp theo, cho phép kiểm soát truy cập và phân quyền theo vai trò.

Cả hai phương pháp đều có ưu điểm riêng. Chúng hoạt động hiệu quả trong việc kiểm soát truy cập theo vai trò. Lựa chọn phương pháp nào tùy thuộc vào yêu cầu dự án của bạn.

Thiết lập dự án Express.js

Để bắt đầu, thiết lập dự án Express.js cục bộ. Sau đó, cài những gói này:

npm install cors dotenv mongoose cookie-parser jsonwebtoken mongodb \
  passport passport-local

Tiếp theo, tạo database MongoDB hoặc thiết lập một nhóm trên MongoDB Atlas. Sao chép kết nối database URL và thêm nó vào file .env trong thư mục gốc của dự án:

CONNECTION_URI="connection URI"

Cấu hình kết nối database

Trong thư mục gốc, tạo file mới utils/db.js, thêm code bên dưới để thiết lập kết nối tới nhóm MongoDB đang chạy trên Atlas bằng Mongoose.

const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.CONNECTION_URI);
    console.log("Connected to MongoDB!");
  } catch (error) {
    console.error("Error connecting to MongoDB:", error);
  }
};

module.exports = connectDB;

Xác định mô hình dữ liệu

Trong thư mục gốc, tạo file mới model/user.model.js và thêm code sau để xác định mô hình dữ liệu cho người dùng bằng Mongoose.

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: String,
  password: String,
  role: String
});

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

Tạo controller cho API Endpoints

Tạo một file controllers/user.controller.js mới trong thư mục gốc và thêm code bên dưới.

Đầu tiên, tạo những import này:

const User = require('../models/user.model');
const passport = require('passport');
const { generateToken } = require('../middleware/auth');
require('../middleware/passport')(passport);

Tiếp theo, xác định logic quản lý đăng ký người dùng và chức năng đăng nhập:

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

  try {
    await User.create({ username, password, role });
    res.status(201).json({ message: 'User registered successfully' });
  } catch (error) {
    console.log(error);
    res.status(500).json({ message: 'An error occurred!' });
  }
};

exports.loginUser = (req, res, next) => {
  passport.authenticate('local', { session: false }, (err, user, info) => {
    if (err) {
      console.log(err);

      return res.status(500).json({
        message: 'An error occurred while logging in'
      });
    }

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

    req.login(user, { session: false }, (err) => {
      if (err) {
        console.log(err);

        return res.status(500).json({
          message: 'An error occurred while logging in'
        });
      }

      const { _id, username, role } = user;
      const payload = { userId: _id, username, role };
      const token = generateToken(payload);
      res.cookie('token', token, { httpOnly: true });
      return res.status(200).json({ message: 'Login successful' });
    });
  })(req, res, next);
};

Hàm registerUser xử lý đăng ký người dùng mới bằng cách truy xuất tên người dùng, mật khẩu và vai trò từ phần nội dung truy vấn. Sau đó nó tạo một mục người dùng mới trong database và phản hồi bằng một thông báo thành công hoặc lỗi nếu có bất kỳ vấn đề xảy ra trong suốt quá trình này.

Mặt khác, hàm loginUser tạo điều kiện cho người dùng bằng cách sử dụng chiến thuật xác thực cục bộ được cung cấp bởi Passport.js. Nó xác thực thông tin của người dùng và trả về token sau khi đăng nhập thành công. Mã thông báo đó được lưu trong cookie cho các yêu cầu xác thực tiếp theo. Nếu có lỗi xảy ra trong suốt quá trình đăng nhập, nó sẽ trả về một thông báo phù hợp.

Cuối cùng, thêm code triển khai logic tìm nạp tất cả dữ liệu của người dùng từ database. Bài viết dùng endpoint này làm route hạn chế để đảm bảo chỉ người dùng được xác thực có quyền admin mới có thể truy cập endpoint này.

exports.getUsers = async (req, res) => {
  try {
    const users = await User.find({});
    res.json(users);
  } catch (error) {
    console.log(error);
    res.status(500).json({ message: 'An error occurred!' });
  }
};

Thiết lập chiến thuật xác thực cục bộ Passport.js

Để xác thực người dùng sau khi họ cung cấp thông tin đăng nhập, bạn cần thiết lập chiến thuật xác thực cục bộ.

Tạo một file middleware/passport.js mới trong thư mục gốc và thêm code sau.

const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/user.model');

module.exports = (passport) => {
  passport.use(
    new LocalStrategy(async (username, password, done) => {
      try {
        const user = await User.findOne({ username });

        if (!user) {
          return done(null, false);
        }

        if (user.password !== password) {
          return done(null, false);
        }

        return done(null, user);
      } catch (error) {
        return done(error);
      }
    })
  );
};

Code này xác định một chiến thuật passport.js cục bộ để xác thực người dùng dựa trên tên người dùng và mật khẩu được cung cấp.

Đầu tiên, nó yêu cầu database để tìm người dùng với username phù hợp và sau đó tiếp tục xác thực mật khẩu. Kết quả, nó trả về đối tượng người dùng được xác thực nếu quá trình đăng nhập thành công.

Tạo middleware xác thực JWT

Bên trong thư mục middleware, tạo một file auth.js mới và thêm code sau để xác định middleware tạo & xác minh JWT.

const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;

const generateToken = (payload) => {
  const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });
  return token;
};

const verifyToken = (requiredRole) => (req, res, next) => {
  const token = req.cookies.token;

  if (!token) {
    return res.status(401).json({ message: 'No token provided' });
  }

  jwt.verify(token, secretKey, (err, decoded) => {
    if (err) {
      return res.status(401).json({ message: 'Invalid token' });
    }

    req.userId = decoded.userId; 

    if (decoded.role !== requiredRole) {
      return res.status(403).json({
        message: 'You do not have the authorization and permissions to access this resource.'
      });
    }

    next();
  });
};

module.exports = { generateToken, verifyToken };

Hàm generateToken tạo JWT với thời gian hết hạn được chỉ định, còn hàm verifyToken kiểm tra xem liệu token có tồn tại và hợp lệ hay không. Ngoài ra, nó cũng xác thực rằng token được giải mã chứa vai trò bắt buộc, về cơ bản, đảm bảo chỉ người dùng có thẩm quyền và được phép mới truy cập được.

Để ký JWT duy nhất theo cách duy nhất, bạn cần tạo khóa bí mật riêng và thêm nó vào file .env như bên dưới.

SECRET_KEY="This is a sample secret key."

Xác định định tuyến API

Trong thư mục gốc, tạo thư mục mới và đặt tên là routes. Trong thư mục này, tạo userRoutes.js và thêm code sau:

const express = require('express');
const router = express.Router();
const userControllers = require('../controllers/userController');
const { verifyToken } = require('../middleware/auth');

router.post('/api/register', userControllers.registerUser);
router.post('/api/login', userControllers.loginUser);

router.get('/api/users', verifyToken('admin'), userControllers.getUsers);

module.exports = router;

Code này xác định các route HTTP cho REST API. Cụ thể, route users hoạt động như route được bảo vệ. Bằng cách hạn chế truy cập tới người dùng với vai trò admin, bạn thực thi hiệu quả quyền truy cập dựa trên vai trò.

Update file server chính

Mở file server.js và update nó như sau:

const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const app = express();
const port = 5000;
require('dotenv').config();
const connectDB = require('./utils/db');
const passport = require('passport');
require('./middleware/passport')(passport);

connectDB();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.use(cookieParser());
app.use(passport.initialize());

const userRoutes = require('./routes/userRoutes');
app.use('/', userRoutes);

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

Cuối cùng, khởi động server lập trình để chạy ứng dụng này.

node server.js

Thực hiện kiểm soát quyền truy cập dựa trên vai trò là cách hiệu quả để nâng cao bảo mật ứng dụng. Hi vọng bài viết giúp bạn thực hiện công việc này dễ dàng hơn.

Thứ Ba, 25/07/2023 16:49
51 👨 157
0 Bình luận
Sắp xếp theo