Sau khi đã cài đặt giao diện đăng ký, giờ là lúc chúng ta có thể cài đặt các trang xác nhận người dùng, trang đăng nhập và trang chủ, công việc này cũng không quá phức tạp vì không có xử lý logic gì nhiều, chỉ bao gồm việc tạo giao diện, gọi API và cập nhật trạng thái từ kết quả API trả về.

Cài đặt màn hình xác nhận

Trước khi cài đặt màn hình xác nhận, chúng ta có thể đăng ký một người dùng với tên đăng nhập là techmaster và mật khẩu là 123456, sau đó quay trở lại terminal của nodejs server chúng ta sẽ thấy activationToken được in ra log, ví dụ của mình là: 17c1c579-5259-459a-85d8-933e6b811622, chúng ta hãy lưu activationToken này lại ở đâu đó.
Bây giờ chúng ta có thể cài đặt màn hình xác nhận với mã nguồn như sau:

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { useParams, useLocation } from 'react-router-dom';
import { SERVER_URL } from './configs/Config';
import './ActivateAccount.css';

const ActivateAccount: React.FC = () => {
  const { username } = useParams<{ username: string }>();
  const location = useLocation();
  const queryParams = new URLSearchParams(location.search);
  const token = queryParams.get('token');

  const [message, setMessage] = useState<string | null>(null);
  const [isSuccess, setIsSuccess] = useState<boolean | null>(null);

  useEffect(() => {
    const activateAccount = async () => {
      if (!token) {
        setMessage('Token is missing.');
        setIsSuccess(false);
        return;
      }

      try {
        const response = await axios.post(`${SERVER_URL}/users/${username}/activate?token=${token}`);
        setMessage(response.data.message || 'Account activated successfully!');
        setIsSuccess(true);
      } catch (error) {
        if (axios.isAxiosError(error) && error.response) {
          setMessage(error.response.data.message || 'Activation failed.');
        } else {
          setMessage('An unknown error occurred.');
        }
        setIsSuccess(false);
      }
    };

    activateAccount();
  }, [username, token]);

  return (
    <div className="activate-account-container">
      <h2>Activate Account</h2>
      {message && (
        <p className={isSuccess === true ? 'success-message' : 'error-message'}>
          {message}
        </p>
      )}
    </div>
  );
};

export default ActivateAccount;

Màn hình này không có gì nhiều, nó chỉ có:

  1. Một giao diện đơn giản với tiêu đề và một thông điệp.
  2. Khi trang đã được tải thì nó sẽ gọi API để xác thực token kích hoạt, nếu đúng thì hiển thị ra thông báo thành công, còn không đúng thì hiển thị thông báo thất bại.
    Chúng ta có thể bổ sung một chút css cho đẹp thông qua tập tin ActivateAccount.css với nội dung như sau:
.activate-account-container {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    text-align: center;
    flex-direction: column;
  }
  
  h2 {
    margin-bottom: 20px;
  }
  
  .success-message, .error-message {
    font-size: 18px;
    margin-top: 20px;
  }

Bây giờ chúng ta có thể truy cập màn hình kích hoạt tài khoản với tên đăng nhập và token kích hoạt đã được lưu trữ, ví dụ của mình là: http://localhost:5173/activate/techmaster?token=17c1c579-5259-459a-85d8-933e6b811622

Chúng ta sẽ thấy tài khoản được kích hoạt thành công.

Cài đặt màn hình login

Sau khi kích hoạt tài khoản thì người dùng có thể đăng nhập và chúng ta sẽ cần phải cài đặt màn hình này với mã nguồn như sau:

import React, { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUser, faLock } from '@fortawesome/free-solid-svg-icons';
import axios from 'axios';
import { Link, useNavigate } from 'react-router-dom';
import { SERVER_URL } from './configs/Config';

interface LoginFormState {
  username: string;
  password: string;
}

const Login: React.FC = () => {
  const [formState, setFormState] = useState<LoginFormState>({
    username: '',
    password: '',
  });

  const [errorMessage, setErrorMessage] = useState<string | null>(null);
  const [successMessage, setSuccessMessage] = useState<string | null>(null);

  const navigate = useNavigate();

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormState({
      ...formState,
      [name]: value,
    });
  };

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setErrorMessage(null);
    setSuccessMessage(null);

    try {
      const response = await axios.post(`${SERVER_URL}/users/login`, formState);
      localStorage.setItem('accessToken', response.data.accessToken);
      setSuccessMessage('Login successful!');
      navigate('/');

    } catch (error) {
      if (axios.isAxiosError(error) && error.response) {
        setErrorMessage(error.response.data.message || 'Login failed.');
      } else {
        setErrorMessage('An unknown error occurred.');
      }
      console.error('Login failed', error);
    }
  };

  return (
    <div className="form-container">
      <form onSubmit={handleSubmit} className="register-form">
        <div className="input-group">
          <FontAwesomeIcon icon={faUser} className="icon" />
          <input
            type="text"
            name="username"
            placeholder="Username"
            value={formState.username}
            onChange={handleChange}
            required
          />
        </div>
        <div className="input-group">
          <FontAwesomeIcon icon={faLock} className="icon" />
          <input
            type="password"
            name="password"
            placeholder="Password"
            value={formState.password}
            onChange={handleChange}
            required
          />
        </div>
        <button type="submit">Login</button>

        {errorMessage && <p className="error-message">{errorMessage}</p>}
        {successMessage && <p className="success-message">{successMessage}</p>}

        <p className="register-link">
          Don't have an account? <Link to="/register">Register</Link>
        </p>
      </form>
    </div>
  );
};

export default Login;

Trong màn hình login này chúng ta có:

  1. Form đăng nhập với một ô nhập tài khoản và một ô nhập mật khẩu.
  2. Khi người dùng nhấn submit, chúng ta sẽ gọi API đăng nhập để kiểm tra, nếu đăng nhập thành công chúng ta sẽ chuyển người dùng ra trang chủ, nếu login thất bại chúng ta sẽ hiển thị ra thông báo.
    Kết quả khi truy cập vào màn hình login http://localhost:5173/login chúng ta sẽ được:

Cài đặt màn hình home

Như đã nhắc đến ở trên, khi người dùng login xong họ sẽ được vào màn hình trang chủ, chúng ta có thể cài đặt màn hình này với mã nguồn như sau:

import React from 'react';
import './Home.css';

const Home: React.FC = () => {
  return (
    <div className="home-container">
      <h1>Welcome to the Home Page</h1>
    </div>
  );
};

export default Home;

Màn hình này cũng không có gì nhiều ngoài một dòng chữ chào mừng, nhưng để cho đẹp hơn chúng ta sẽ bổ sung một chút CSS thông qua tập tin Home.css với nội dung như sau:

.home-container {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    text-align: center;
    flex-direction: column;
}

h1 {
    margin-bottom: 20px;
}

Truy cập vào màn hình trang chủ http://localhost:5173/ chúng ta sẽ có:

Cài đặt ProtectedRoute

Mọi thứ có vẻ ổn nhưng thực ra đang có vấn đề đó là chúng ta chưa hề login mà đã có thể vào trang chủ rồi, điều này có thể gây ra các vấn đề bảo mật, chính vì vậy chúng ta sẽ cần cài đặt ProtectedRoute để đánh chặn các yêu cầu truy cập vào các tài nguyên cần được bảo vệ với mã nguồn như sau:

import React, { useEffect, useState } from 'react';
import { Navigate } from 'react-router-dom';
import axios from 'axios';
import { SERVER_URL } from './configs/Config';

interface ProtectedRouteProps {
  children: React.ReactNode;
}

const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
  const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);

  useEffect(() => {
    const validateToken = async () => {
      try {
        await axios.post(`${SERVER_URL}/users/validate-access-token`, {}, {
            headers: {
                'X-AccessToken': localStorage.getItem('accessToken')
            }
        });
        setIsAuthenticated(true);
      } catch (error) {
        setIsAuthenticated(false);
      }
    };

    validateToken();
  }, []);

  if (isAuthenticated === null) {
    return <div className="loading">Loading...</div>;
  }

  return isAuthenticated ? <>{children}</> : <Navigate to="/login" replace />;
};

export default ProtectedRoute;

Ở đây chúng ta đang gọi API kiểm tra access token, nếu hợp lệ chúng ta sẽ cho phép người dùng truy cập vào các trang được bảo vệ, ngược lại chúng ta sẽ chuyển hướng người dùng ra màn hình login.
Ngoài ra chúng ta cũng sẽ cần cập nhật lại tập tin App.tsx với mã nguồn như sau:

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Register from './Register';
import ActivateAccount from './ActivateAccount';
import Login from './Login';
import Home from './Home';
import ProtectedRoute from './ProtectedRoute';

function App() {
  return (
    <Router>
        <Routes>
          <Route path="/register" element={<Register />} />
          <Route path="/activate/:username" element={<ActivateAccount />} />
          <Route path="/login" element={<Login />} />
          <Route
            path="/"
            element={
              <ProtectedRoute>
                <Home />
              </ProtectedRoute>
            }
        />
        </Routes>
    </Router>
  );
}

export default App

Bây giờ chúng ta có thể đăng nhập với tài khoản techmaster và mật khẩu 123456 sau đó sẽ được tự động chuyển đến màn hình home.

Tổng kết

Như vậy chúng ta đã cùng nhau cài đặt xong luồng xác thực với ReactJS và NodeJS.


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