Luồng xác thực từ lúc đăng ký, xác nhận, đăng nhập cho đến lúc vào được trang chủ là một trong những chức năng kinh điển của mọi hệ thống có cho phép người dùng đăng nhập. Nghe thì có vẻ đơn giản nhưng ẩn chứa bên trong nó là sự phức tạp, chúng ta sẽ sử dụng NodeJS cho phần cài đặt phía máy chủ và ReactJS cho phần client nhé.

Import thư viện

Đầu tiên chúng ta cần import các thư viện cần thiết:

const express = require('express');
const cors = require('cors');
const app = express();
const cookieParser = require('cookie-parser');
const { v4: uuidv4 } = require('uuid');

app.use(cors());
app.use(express.json());
app.use(cookieParser());

Ở đây chúng ta có:

  1. Thư viện express cho http server.
  2. Thử viện cors cung cấp middleware để giải quyết vấn đề CORS gọi API giữa các domain khác nhau.
  3. Thư viện cookie-parser để cung cấp middleware cho tính năng parse và lấy giá trị từ cookie.
  4. Thư viện uuid để tạo ngẫu nhiên một chuỗi uuid.

Cài đặt API

Cài đặt API đăng ký

Vì đây là một ví dụ minh hoạ cho ReactJS nên mình sẽ không cài đặt API có sử dụng database cho đơn giản, thay vào đó mình sẽ sử dụng dữ liệu dạng cache được lưu trên RAM, mỗi khi chúng ta khởi động chương trình thì dữ liệu sẽ bị mất, tuy nhiên dành cho mục đích demo như vậy là đủ, mã nguồn cài đặt cho API đăng ký sẽ như sau:

const userByUsername = {
    tvd12: {
      username: 'tvd12',
      email: 'itprono3@gmail.com',
      password: '123456',
      displayName: 'Ta Van Dung'
    }
  };
const userNameByActivationToken = {};
const usernameByAccessToken = {};
const accessTokenByUsername = {};

app.post('/users/register', (req, res) => {
    const { username, email, password, displayName } = req.body;
    userByUsername[username] = {
        username,
        email,
        password,
        displayName
    };
    const activationToken = uuidv4();
    userNameByActivationToken[username] = activationToken;
    console.log('activationToken:', activationToken);
    res.status(201).json({savedUsername: username});
});

Ở đây chúng ta có:

  1. userByUsername: Lưu thông tin người dùng bao gồm tên đăng nhập, email, mật khẩu và tên hiển thị ánh xạ với tên đăng nhập.
  2. userNameByActivationToken: Lưu thông tin token kích hoạt tài khoản ánh xạ với tên đăng nhập.
  3. usernameByAccessToken: Lưu thông tin acess token với tên đăng nhập.
  4. accessTokenByUsername: Lưu thông tin tên đăng nhập ánh xạ với access token.
  5. Uri của API là /users/register.
  6. Người dùng sẽ gửi thông tin đăng ký tài khoản với đầy đủ tên đăng nhập, email, mật khẩu và tên hiển thị, chúng ta sẽ lưu thông tin người dùng, tạo ngẫu nhiên một chuỗi uuid làm token kích hoạt tài khoản cho người dùng sau đó trả về trạng thái đã được tạo 201 cho người dùng. Trong dự án thực tế, ở bước này chúng ta sẽ cần phải gửi email cho người dùng với đường dẫn kích hoạt, tuy nhiên với mục đích demo tôi sẽ bỏ qua bước này.

Cài đặt API xác nhận tài khoản

Tài khoản sau khi được đăng ký sẽ cần được kích hoạt thì mới có thể bắt đầu sử dụng được, mã nguồn cài đặt API kích hoạt tài khoản sẽ như sau:

app.post('/users/:username/activate', (req, res) => {
    const username = req.params.username;
    const token = req.params.token;
    const storedUsername = userNameByActivationToken[token];
    userByUsername[username].activated = true;
    res.json({correct: username === storedUsername});
});

Ở đây chúng ta sẽ lấy tên tài khoản được ánh xạ với token kích hoạt, sau đó so sánh với tên tài khoản trong đường dẫn yêu cầu, nếu giống nhau nghĩa là tài khoản hợp lệ và được kích hoạt.

app.post('/users/login', (req, res) => {
    const { username, password } = req.body;
    const user = userByUsername[username];
    if (!user || user.password !== password || !user.activated) {
        res.status(404).json({credential: 'invalid'});
        return;
    }
    const accessToken = accessTokenByUsername[username] || uuidv4();
    usernameByAccessToken[accessToken] = username;
    accessTokenByUsername[username] = accessToken;
    res.cookie('X-AccessToken', accessToken, {
        maxAge: 24 * 60 * 60 * 1000,
        httpOnly: true,
        path: '/'
    });
    res.json({accessToken: accessToken});
});

Ở đây chúng ta kiểm tra xem có người dùng nào tồn tại với tên đăng nhập được gửi lên hay không, nếu có thì kiểm tra mật khẩu và trạng thái đã được kích hoạt hay chưa, nếu tất cả điều kiện được đáp ứng thì cấp token cho người dùng. Ở đây chúng ta cần lưu ý:

  1. Trong dự án thực tế mật khẩu sẽ được mã hoá bằng một trong các thuật toán băm, ví dụ md5.
  2. Ở đây mình set access token vào cả cookie lẫn trả về access token cho người dùng. Trong dự án thực tế thì có thể chỉ cần set vào cookie là đủ.

Cài đặt API kiểm tra access token

Trong quá trình hoạt động của ứng dụng, client có thể cần phải kiểm tra tính hợp lệ của access token được cấp để chuyển người dùng qua màn hình login nếu cần thiết, mã nguồn cài đặt của API kiểm tra access token sẽ như sau:

app.post('/users/validate-access-token', (req, res) => {
    const accessToken = req.cookies['X-AccessToken'] || req.get('X-AccessToken');
    const username = usernameByAccessToken[accessToken] || '';
    if (username.length) {
        res.json({valid: true});
    } else {
        res.status(401).json({valid: false});
    }
});

Ở đây chúng ta lấy access token từ cookie hoặc header sau đó kiểm tra xem có người dùng nào được ánh xạ với access token đó không, nếu có thì trả về hợp lệ, ngược lại thì trả về trạng thái 401 không có quyền.

Cài đặt API lấy thông tin người dùng

Đây là một API thông thường sẽ được sử dụng để lấy thông tin người dùng để hiển thị chi tiết nên mình cũng muốn cài đặt ở mức đơn giản như sau:

app.get('/users/:username', (req, res) => {
    const username = req.params.username;
    const accessToken = req.cookies['X-AccessToken'] || req.get('X-AccessToken');
    const actualUsername = usernameByAccessToken[accessToken] || '';
    if (username === actualUsername) {
        res.json(userByUsername[username]);
    } else {
        res.status(401).json({valid: false});
    }
});

Ở đây chúng ta cũng kiểm tra access token và so khớp tên đăng nhập trong yêu cầu và tên đăng nhập thực tế được lấy từ access token, nếu khớp thì trả về thông tin người dùng. Trên thực tế chúng ta chúng ta chỉ cần lấy tên người dùng qua access token là đủ, bước so khớp tên đăng nhập có vẻ hơi thừa.

Tổng kết

Như vậy chúng ta đã cùng nhau cài đặt các API xác thực bằng NodeJS tương đối đơn giản không sử dụng cơ sở dữ liệu, trong bài sau chúng ta sẽ cài đặt từng phần phía client ReactJS nhé.


Cám ơn bạn đã quan tâm đến bài viết này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a