Authentication & Authorization trong ReactJS

6 min read

ReactJS

Authentication và Authorization là một phần quan trọng trong việc phát triển phần mềm, giúp chúng ta xác thực và phân quyền người dùng trước khi cho người dùng truy cập vào tài nguyên của ứng dụng. Trong bài viết này sẽ hướng dẫn các ReactJS thủ 🤣 cách implement Authentication và Authorization. A chị nào biết rồi giả bộ đọc hết bài viết rồi so sánh với cách đang dùng xem thế nào ha :))

Nẹt bô rồi gẹt gô thôi ReactJS thủ 🤣
https://react.dev/

1. Đặt vấn đề:

  • Làm thế nào để redirect user về một page sau khi sign in success?
  • Làm thế nào khi reload trang thì vẫn giữ trạng thái nếu đã authenticated và ngược lại?
  • Ví dụ web quản trị nhưng có những page chỉ quản trị cấp cao Super Admin mới có thể truy cập được, còn mấy ông quản trị Admin không được quyền truy cập. Tránh tình trạng sau này sắp bị đuổi việc mấy ông vào xoá tài liệu/ bài viết của trang web như… 😀

2. Ý tưởng

  • Sử dụng Context làm global store quản lý user state, việc này cho phép toàn bộ app có thể access user data.
  • Context Provider để wrap app, việc này hỗ trợ check phiên đăng nhập của user ngay bên trong Provider trước khi render bất kỳ page nào trong toàn app.
  • Bên cạnh tạo ra Context Provider để wrap app thì còn một số custom component để wrap layout/ page sẽ đề cập khi triển khai.

3. Triển khai

Lưu ý: Bài viết này không đề cập đến JWT được lưu ở đâu thì bảo mật, không đề cập đến Securiry. Nên lưu ở đâu là tuỳ mấy phen hen :))

3.1. Setup

  • Bài viết này sử dụng ReactJS v18.2.0 và React Router Dom v6.16.0

Do quá lười nên a chị tự initialize dự án nha, ReactJS thủ thì cái này quá easy ha :))

3.2. Create UI

  • Trang Sign In src/modules/auth/sign-in/index.tsx:

Đơn giản chỉ 1 button để gọi xuống service, bệnh lười lên ngôi nên các page khác vẫn vậy :))

ReactJS
ReactJS
  • Trang User List src/modules/dashboard/user/list/index.tsx :
ReactJS
ReactJS
  • Trang User Edit src/modules/dashboard/user/edit/index.tsx:
ReactJS
ReactJS
  • Config router src/modules/router.tsx:

Nhớ bọc BrowserRouter ở src/main.tsx nha quí dị :))

ReactJS

return Router component bên trong src/App.tsx. Xong rồi đó, mà xong mỗi UI thôi, giờ thì user có thế access bất kỳ screen nào của app. Tiếp theo cần xử lý logic phần authentication và authorization.

3.3. Create Auth Context & Provider:

  • Xác định state (AuthState), mình cần 1 object gồm các field:
    • isInitialized: dùng để show loading trong qúa trình fetch user data khi người dùng reload trang.
    • isAuthenticated: dùng để check xem user đã đăng nhập chưa, phiên đăng nhập còn khả dụng hay không?
    • user: tất nhiên là user data rồi :))

src/contexts/auth/types.ts

ReactJS
  • Xác định xong state thì createContext với initialState:

src/contexts/auth/AuthContext.tsx

ReactJS
  • Xác định, khởi tạo action và reducer, như ảnh trên có định nghĩa enum AuthActionType:
    • INITIALIZE – dispatch khi user reload trang web: Set isInitialized: true ẩn Loading sau khi fetch user data, dù có phiên đăng nhập hay không, để còn show ra trang sign in chứ k ẩn sao thấy mà sign in :)). Nếu tồn tại phiên đăng nhập thì set isAuthenticated: true và user data.
    • SIGN_IN – dispatch khi sign in success. Set isAuthenticated: true và user data.
    • SIGN_OUT – dispatch khi sign out: Xoá phiên đăng nhập (token/call sign-out api,...)

Sau khi xác định được action thì khởi tạo action và reducer:

src/contexts/auth/reducers.ts

ReactJS
  • Create AuthProvidersrc/contexts/auth/AuthContext.tsx
ReactJS

và custom hook: src/hooks/useAuth.ts

ReactJS
  • Nhớ bọc app lại với AuthProvider nha, hông là lỗi ráng chịu à :))
  • Trong AuthProvider có 1 IIFE bên trong useEffect được thực thi 1 lần khi user reload trang, nhằm check xem có phiên đăng nhập đang tồn tại hay không bằng cách check token và get profile. Trong quá trình check phiên đăng nhập và get profile thì sẽ show loading nếu chưa isInitialized (false). Sau khi IIFE thực thi xong thì auth state đã được update tương ứng với kết quả được dispatch bên trong IIFE, khi quá trình INITIALIZE hoàn tất thì isInitialized được update thành true và ẩn đi loading – INITIALIZE function trong reducerHandlers object.

3.4. Sign In

ReactJS
ReactJS
  • Sign in đơn giản thì mình tạo một button để call 1 sign in function và resolve data mẫu. Sau khi Sign in success thì dispatch(signIn({ user })). Lúc này auth state đã được update isAuthenticated: true và có user data.

Ủe toàn logic rồi tui redirect user qua lại các màn hình chỗ nào đâu? 🤣 Như này tui thấy có bọc app lại với AuthProvider thì user access screen nào cụng đc mà :))

Như có đề cập ngay ý tưởng ban đầu Bên cạnh tạo ra Context Provider để wrap app thì còn một số custom component để wrap layout/ page sẽ đề cập khi triển khai.

Nẹt bô gẹt gô…

3.5. “Thuê bảo vệ”

Không cho user access vô tội vạ thì mình cần “thuê bảo vệ” để canh giúp chứ ai đâu mà canh được :)). Ví dụ trong các công ty muốn ra vào cổng phải có thẻ nhân viên chẳng hạn thì bác “bảo vệ” mới cho vào cổng. Nếu có thẻ nhân viên rồi mà không có bảo vệ thì có cái thẻ cụng như không… Giống như vấn đề đang xử lý trong bài viết nếu đã sign in success hay là load profile success thì tiếp đến cần “bác bảo vệ”.

Guard – Đóng vai trò như “bác bảo vệ” giúp app kiểm xoát quyền của user trước và sau xác thực, cơ bản mình sẽ tạo ra 3 guards:

  • GuestGuard bọc lấy các screen mà sau khi sign-in không được access như là sign-in, sign-up. Bên trong component này loading nếu đang trong quá trình INITIALIZE vì tại thời điểm fetch data dựa vào isAuthenticated là không đủ để chắc chắn rằng user còn phiên đăng nhập hay không. Và đây chính là nơi mà sau khi sign-in success isAuthenticated: true user sẽ được chuyển đến trang được yêu cầu sign-in, vì dùng global state, nên khi sign in success –> dispatch action –> state update –> component re-render.

src/guards/GuestGuard.tsx

ReactJS
  • Ngược lại với GuestGuardAuthGuard bọc lấy các screen yêu cầu sign-in mới có thể access. nếu user access các trang này mà chưa được xác thực (isAuthenticated: false) thì sẽ redirect user về sign in. Nếu đã được xác thực (isAuthenticated: true) thì render đúng trang mà nó đang bọc lấy.

src/guards/AuthGuard.tsx

ReactJS
  • RoleBasedGuard là quá trình diễn ra sau Authentication, dùng để check xem user có đủ quyền truy cập vào tài nguyên app hay không, quá trình này là Authorization hay còn được gọi là Role Base Access Control. Nếu user không được phép truy cập thì show warning hoặc là redirect sang trang khác (403). Trong component này nhận lấy props là accessibleRoles sau đó bọc lấy các page cần phân quyền, khi bọc thì chỉ định những role nào được truy cập và page này thông qua accessibleRoles.

Lưu ý: RoleBasedGuard sẽ được bọc bên trong AuthGuard

src/guards/RoleBasedGuard.tsx

ReactJS

Sau khi bọc các page với guards ta được config router:

src/modules/router.tsx

ReactJS

4. Tổng kết

  • Cách implement với ReactJS này tách biệt việc handle sign-in với việc quản lý phiên đăng nhập của user, sign-in không cần quan tâm sau khi sign-in xong ứng dụng sẽ xảy ra hành vi gì tiếp theo, việc quản lý phiên đăng nhập cụng không cần biết sign-in đăng nhập như thế nào.
  • Nếu sau này dự án cần thêm các module có yêu cầu authrole base access control thì chỉ cần wrap các module mới với guard.
  • Là bài đầu tay của một chiếc dev non, xuất phát từ quá trình đi làm thấy nhiều mem xử lý auth hay bị lỗi nên mới có cảm hứng viết bài viết này, nếu có sai sót mong a/c góp ý.
Avatar photo

Leave a Reply

Your email address will not be published. Required fields are marked *