Một trong những vấn đề quan trọng nhất khi xây dựng một RESTful API là đảm bảo rằng chỉ những người dùng có quyền được truy cập các tài nguyên của app (API). Tuy nhiên, khi bạn phát triển ứng dụng phân phối, có rất nhiều vấn đề liên quan đến việc xác thực và ủy quyền.

Trong bài viết này, chúng ta sẽ thảo luận về cách giải quyết vấn đề này bằng cách sử dụng JSON Web Tokens (JWT) và thư viện Passport.js.

Giới thiệu về JSON Web Tokens (JWT)

JSON Web Tokens (JWT) là một tiêu chuẩn mở được sử dụng để truyền tải thông tin xác thực giữa các bên một cách an toàn và đáng tin cậy. JWT thường được sử dụng trong các ứng dụng web và API để xác thực người dùng và phân quyền truy cập các tài nguyên.

JWT có 3 phần cơ bản: header, payloadsignature:

  • Header chứa thông tin về loại token và thuật toán được sử dụng để tạo signature.
  • Payload chứa thông tin về người dùng và các thông tin liên quan đến xác thực.
  • Signature được sử dụng để xác nhận tính hợp lệ của token.

Một đặc điểm của JWT là nó là một chuỗi dài được mã hóa, nghĩa là nó có thể được truyền tải an toàn thông qua các kênh không an toàn như HTTP (đương nhiên bạn vẫn nên dùng HTTPS cho sản phẩm của mình nhé!).

Giới thiệu về Passport.js

Passport là một thư viện xác thực và ủy quyền cho Node.js. Nó được sử dụng để xác thực thông tin đăng nhập của người dùng và ủy quyền quyền truy cập cho các tài nguyên cụ thể.

Passport hỗ trợ nhiều loại xác thực khác nhau, bao gồm các cơ chế xác thực cơ bản: OAuth, OpenID và các cơ chế phức tạp hơn.

Chuẩn bị

Đầu tiên, chúng ta cần có một RESTful API được xây dựng bằng Node.js và Express. Trong bài viết này, chúng ta sẽ tạo một ứng dụng Notes, cho phép người dùng tạo, xem, sửa đổi và xóa các ghi chú. Bạn có thể sử dụng một thư viện ORM như Sequelize hoặc Mongoose để quản lý cơ sở dữ liệu.

Bước 1: Cài đặt Passport.js và các plugin liên quan

Để bắt đầu sử dụng Passport.js, chúng ta cần cài đặt các plugin liên quan, bao gồm passport, passport-jwtpassport-local.

npm install passport passport-jwt passport-local

Bước 2: Tạo chiến lược xác thực Passport.js

Chúng ta cần phải tạo một chiến lược xác thực để Passport.js có thể xác thực người dùng.

Trong bài này, chúng ta sử dụng JWT để xác thực người dùng.

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const config = require('./config/config');
const models = require('./models');
const localOptions = { usernameField: 'email' };

const localLogin = new LocalStrategy(localOptions, (email, password, done) => {
  models.users.findOne({ where: { email } })
    .then(user => {
      if (!user) {
        return done(null, false, { error: 'Invalid email or password.' });
      }
      if (!user.validPassword(password)) {
        return done(null, false, { error: 'Invalid email or password.' });
      }
      return done(null, user);
    })
    .catch(err => done(err));
});

const jwtOptions = {
  jwtFromRequest: ExtractJwt.fromHeader('authorization'),
  secretOrKey: config.secret
};

const jwtLogin = new JwtStrategy(jwtOptions, (payload, done) => {
  models.users.findById(payload.sub)
    .then(user => {
      if (user) {
        done(null, user);
      } else {
        done(null, false);
      }
    })
    .catch(err => done(err, false));
});

passport.use(localLogin);
passport.use(jwtLogin);

Trong đoạn code trên, chúng ta đã định nghĩa 2 chiến lược xác thực: localLoginjwtLogin. Chiến lược localLogin sử dụng chiến lược địa phương/local để xác thực người dùng dựa trên email và password. Chiến lược jwtLogin sử dụng JWT để xác thực người dùng.

Trong chiến lược jwtLogin, chúng ta đã định nghĩa jwtOptions để cấu hình cách trích xuất JWT từ yêu cầu và khóa bí mật được sử dụng để xác nhận signature. Trong ví dụ này, chúng ta đã lưu khóa bí mật trong tệp cấu hình config.

Bước 3: Xây dựng API

Sau khi đã cài đặt và cấu hình Passport.js, chúng ta phải xử lý các yêu cầu API để xác thực người dùng. Trong ví dụ này, chúng ta sẽ xây dựng các endpoint để quản lý danh sách ghi chú.

const express = require('express');
const bodyParser = require('body-parser');
const passport = require('passport');
const config = require('./config/config');
const models = require('./models');
const app = express();

app.use(bodyParser.json());

app.post('/api/login', (req, res) => {
  const email = req.body.email;
  const password = req.body.password;

  models.users.findOne({ where: { email } })
    .then(user => {
      if (!user) {
        return res.status(401).json({ error: 'Invalid email or password.' });
      }
      if (!user.validPassword(password)) {
        return res.status(401).json({ error: 'Invalid email or password.' });
      }
      const token = jwt.sign({ sub: user.id }, config.secret);
      res.json({ token });
    })
    .catch(err => res.status(500).json({ error: 'Internal server error.' }));
});

app.post('/api/notes', passport.authenticate('jwt', { session: false }), (req, res) => {
  const title = req.body.title;
  const body = req.body.body;
  const userId = req.user.id;

  models.notes.create({ title, body, userId })
    .then(note => res.json(note))
    .catch(err => res.status(500).json({ error: 'Internal server error.' }));
});

app.get('/api/notes', passport.authenticate('jwt', { session: false }), (req, res) => {
  const userId = req.user.id;

  models.notes.findAll({ where: { userId } })
    .then(notes => res.json(notes))
    .catch(err => res.status(500).json({ error: 'Internal server error.' }));
});

app.get('/api/notes/:id', passport.authenticate('jwt', { session: false }), (req, res) => {
  const id = req.params.id;
  const userId = req.user.id;

  models.notes.findOne({ where: { id, userId } })
    .then(note => {
      if (!note) {
        return res.status(404).json({ error: 'Note not found.' });
      }
      res.json(note);
    })
    .catch(err => res.status(500).json({ error: 'Internal server error.' }));
});

app.put('/api/notes/:id', passport.authenticate('jwt', { session: false }), (req, res) => {
  const id = req.params.id;
  const userId = req.user.id;
  const title = req.body.title;
  const body = req.body.body;

  models.notes.findOne({ where: { id, userId } })
    .then(note => {
      if (!note) {
        return res.status(404).json({ error: 'Note not found.' });
      }
      note.update({ title, body })
        .then(note => res.json(note))
        .catch(err => res.status(500).json({ error: 'Internal server error.' }));
    })
    .catch(err => res.status(500).json({ error: 'Internal server error.' }));
});

app.delete('/api/notes/:id', passport.authenticate('jwt', { session: false }), (req, res) => {
  const id = req.params.id;
  const userId = req.user.id;

  models.notes.findOne({ where: { id, userId } })
    .then(note => {
      if (!note) {
        return res.status(404).json({ error: 'Note not found.' });
      }
      note.destroy()
        .then(() => res.json({ message: 'Note deleted.' }))
        .catch(err => res.status(500).json({ error: 'Internal server error.' }));
    })
    .catch(err => res.status(500).json({ error: 'Internal server error.' }));
});

app.listen(3000, () => {
  console.log('Server listening...');
});

Trong đoạn mã trên, chúng ta đã định nghĩa các API để quản lý các ghi chú. Trong chi tiết, chúng ta đã định nghĩa các endpoint /api/login để xác thực người dùng và trả về JWT cho người dùng, /api/notes để tạo một ghi chú mới, /api/notes/:id để lấy thông tin về một ghi chú cụ thể, /api/notes/:id để cập nhật một ghi chú và /api/notes/:id để xóa một ghi chú.

Chúng ta đã thêm middleware passport.authenticate('jwt', { session: false }) để xác thực người dùng trước khi cho phép họ truy cập các API liên quan đến ghi chú.

Kết luận

Trên đây là bài hướng dẫn về cách sử dụng JSON Web Tokens (JWT) và thư viện Passport.js để đảm bảo rằng chỉ những người dùng được phép truy cập các tài nguyên. Sử dụng các công nghệ này giúp bảo vệ ứng dụng của bạn trước các cuộc tấn công xâm nhập nhằm phá vỡ dữ liệu. Nếu bạn đang phát triển ứng dụng RESTful API, hãy xem xét áp dụng cách tiếp cận này để đảm bảo rằng ứng dụng của bạn luôn được bảo mật và an toàn.

Leave a Reply

Discover more from Bệ Phóng Việt

Subscribe now to keep reading and get access to the full archive.

Continue reading