Xác thực (Authentication) và phân quyền (Authorization) là những yếu tố quan trọng trong các ứng dụng web. Thay vì xử lý chúng thủ công trong nhiều thành phần (components), chúng ta có thể tạo một hook có thể tái sử dụng để đơn giản hóa logic.
Trong bài viết này, chúng ta sẽ triển khai logic xác thực trực tiếp trong một component trước, sau đó sẽ refactor (tái cấu trúc) nó bằng cách sử dụng hook useAuth
tùy chỉnh.
Logic xác thực không có Hook tùy chỉnh
Hãy bắt đầu bằng cách tạo một thành phần React để quản lý xác thực người dùng theo cách thủ công.
import React, { useState } from "react"; const AuthComponent = () => { const [user, setUser] = useState(null); const login = (username) => { setUser({ name: username, role: "user" }); }; const logout = () => { setUser(null); }; return ( <div> {user ? ( <> <h2>Welcome, {user.name}!</h2> <button onClick={logout}>Logout</button> </> ) : ( <button onClick={() => login("John Doe")}>Login</button> )} </div> );
}; export default AuthComponent;
Các vấn đề với cách tiếp cận này
- Logic xác thực bị ràng buộc với một thành phần duy nhất, khiến việc tái sử dụng trở nên khó khăn.
- Không có cách tập trung nào để quản lý trạng thái người dùng trên toàn bộ ứng dụng.
- Kiểm tra quyền hạn cần phải được thực hiện thủ công trong nhiều thành phần.
Tạo một Hook useAuth
tùy chỉnh
Để xác thực có thể tái sử dụng, chúng ta sẽ tạo một Hook useAuth
quản lý đăng nhập, đăng xuất và trạng thái người dùng trên toàn cầu.
1. AuthProvider (Cung cấp ngữ cảnh)
import { useState, createContext } from "react"; export const AuthContext = createContext(null); export const AuthProvider = ({ children }) => { const [user, setUser] = useState(() => { const storedUser = localStorage.getItem("user"); return storedUser ? JSON.parse(storedUser) : null; }); const login = (username, role) => { const userData = { name: username, role }; setUser(userData); localStorage.setItem("user", JSON.stringify(userData)); }; const logout = () => { setUser(null); localStorage.removeItem("user"); }; return ( <AuthContext.Provider value={{ user, login, logout }}> {children} </AuthContext.Provider> );
};
2. Hook useAuth
import { useContext } from "react";
import { AuthContext } from "./AuthProvider"; export const useAuth = () => { const { user, login, logout } = useContext(AuthContext); const isAuthorized = (requiredRole) => { return user && user.role === requiredRole; }; return { user, login, logout, isAuthorized };
3. Thuận lợi
- Có thể sử dụng trên nhiều component.
- Cung cấp trạng thái xác thực tập trung.
- Loại bỏ logic xác thực dư thừa.
Sử dụng useAuth
trong một Component
Hãy cấu trúc lại thành phần xác thực của chúng ta để sử dụng Hook useAuth
.
import React from "react";
import { useAuth } from "./useAuth"; const AuthComponent = () => { const { user, login, logout } = useAuth(); return ( <div> {user ? ( <> <h2>Welcome, {user.name}!</h2> <button onClick={logout}>Logout</button> </> ) : ( <button onClick={() => login("John Doe", "user")}>Login</button> )} </div> );
}; export default AuthComponent;
Bao bọc thành phần gốc bằng AuthProvider
Để xác thực khả dụng trên toàn bộ ứng dụng, hãy bọc thành phần gốc của bạn bằng AuthProvider
.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { AuthProvider } from "./AuthProvider";
import App from "./App"; const rootElement = document.getElementById("root");
const root = createRoot(rootElement); root.render( <StrictMode> <AuthProvider> <App /> </AuthProvider> </StrictMode>
);
Ví dụ: Bảo vệ các Admin Routes
import React from "react";
import { useAuth } from "./useAuth"; const AdminPanel = () => { const { user, isAuthorized } = useAuth(); if (!user || !isAuthorized("admin")) { return <h2>Access Denied</h2>; } return <h2>Welcome to the Admin Panel</h2>;
}; export default AdminPanel;
Ví dụ: Bảo vệ các Routes bằng thành phần bậc cao hơn (HOC)
import React from "react";
import { useAuth } from "./useAuth";
import { Navigate } from "react-router-dom"; const ProtectedRoute = ({ children, requiredRole }) => { const { user, isAuthorized } = useAuth(); if (!user || (requiredRole && !isAuthorized(requiredRole))) { return <Navigate to="/login" replace />; } return children;
}; export default ProtectedRoute;
Thuận lợi:
- Đơn giản hóa việc kiểm soát truy cập dựa trên vai trò.
- Tránh việc kiểm tra quyền hạn nhiều lần.
- Dễ dàng bảo vệ các tuyến đường bằng một thành phần
ProtectedRoute
có thể tái sử dụng.
Hy vọng thông tin vừa rồi sẽ hữu ích đối với các bạn!