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
reloadtrang thì vẫn giữ trạng thái nếu đãauthenticatedvà ngược lại? - Ví dụ web quản trị nhưng có những page chỉ quản trị cấp cao
Super Adminmới có thể truy cập được, còn mấy ông quản trịAdminkhô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
Contextlàm global store quản lýuser state, việc này cho phép toàn bộ app có thể accessuser 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 để wraplayout/ pagesẽ đề 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.0và React Router Domv6.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 Insrc/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 :))


- Trang
User Listsrc/modules/dashboard/user/list/index.tsx:


- Trang
User Editsrc/modules/dashboard/user/edit/index.tsx:


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

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

- Xác định xong state thì
createContextvớiinitialState:
src/contexts/auth/AuthContext.tsx

- Xác định, khởi tạo
actionvàreducer, như ảnh trên có định nghĩa enumAuthActionType:INITIALIZE– dispatch khi userreloadtrang web: SetisInitialized: 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ì setisAuthenticated: truevàuser data.SIGN_IN– dispatch khi sign insuccess. SetisAuthenticated: truevà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

- Create
AuthProvider:src/contexts/auth/AuthContext.tsx

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

- Nhớ bọc app lại với
AuthProvidernha, hông là lỗi ráng chịu à :)) - Trong
AuthProvidercó 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áchcheck token và get profile. Trong quá trìnhcheck phiên đăng nhập và get profilethì sẽ showloadingnếu chưaisInitialized (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ìnhINITIALIZEhoàn tất thìisInitializedđược update thànhtruevà ẩn đi loading –INITIALIZE functiontrongreducerHandlers object.
3.4. Sign In


- Sign in đơn giản thì mình tạo một button để call 1
sign in functionvà resolve data mẫu. Sau khi Sign insuccessthìdispatch(signIn({ user })). Lúc nàyauth stateđã được updateisAuthenticated: truevà 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
AuthProviderthì 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:
GuestGuardbọc lấy cácscreen mà sau khi sign-in không được accessnhư làsign-in, sign-up. Bên trong component này loading nếu đang trong quá trìnhINITIALIZEvì tại thời điểm fetch data dựa vàoisAuthenticatedlà 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 successisAuthenticated: trueuser sẽ được chuyển đếntrang đượ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

- Ngược lại với
GuestGuard,AuthGuardbọc lấy cácscreen yêu cầu sign-inmớ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

RoleBasedGuardlà quá trình diễn ra sauAuthentication, 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àAuthorizationhay 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àaccessibleRolessau đó bọc lấy các page cần phân quyền, khi bọc thì chỉ định nhữngrolenào được truy cập và page này thông quaaccessibleRoles.
Lưu ý:
RoleBasedGuardsẽ được bọc bên trongAuthGuard
src/guards/RoleBasedGuard.tsx

Sau khi bọc các page với guards ta được config router:
src/modules/router.tsx

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
auth,role base access controlthì chỉ cần wrap các module mới vớiguard. - 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ý
authhay 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 ý.
