iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
佛心分享-IT 人職涯歷練

從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始系列 第 27 篇

Day 27|把按鈕藏起來就算有權限控管嗎?Frontend Permission 和 Backend Authorization 到底差在哪?

  • 分享至 

  • xImage
  •  

做後台系統時,很常會遇到這種需求:

只有某些角色可以編輯

只有 Admin 可以刪除

沒有權限的人看不到某個按鈕

前端第一個反應通常會是:

{canEdit && (
  <Button>Edit</Button>
)}

看起來很合理。

沒有權限:

看不到按鈕

有權限:

看得到按鈕

但如果做到這裡就覺得:

權限控管完成了。

其實還不夠。

因為:

UI 上看不到,不代表 API 真的不能被呼叫。


先看最簡單的前端權限

例如:

const canEdit = role === "ADMIN";

return (
  <>
    {canEdit && (
      <Button>Edit</Button>
    )}
  </>
);

這段程式做的是:

role 是 ADMIN?
↓
Yes
→ 顯示 Edit Button

No
→ 不 Render Button

這是一種:

Frontend Permission

它控制的是:

使用者在 UI 上可以看到什麼、操作什麼。


這對 UX 很重要

假設使用者沒有編輯權限。

如果畫面還是顯示:

Edit
Delete
Approve

結果點下去全部:

403 Forbidden

使用者體驗會很差。

所以前端會根據權限:

隱藏按鈕
disable 欄位
隱藏頁籤
禁止進入某些頁面

這些都是合理的。

但它們主要是在處理:

UI / UX

不是完整的 Security Boundary。


為什麼只藏按鈕不夠?

因為前端程式是在使用者的瀏覽器裡執行。

使用者不一定只能透過:

你畫面上的 Button

呼叫 API。

例如:

Frontend
↓
PATCH /users/123

即使畫面上沒有:

Edit Button

只要有人知道這支 API,

理論上仍然可以嘗試:

直接送 HTTP Request

例如透過:

DevTools
API Client
其他程式

所以:

把 Button 藏起來,只是拿掉操作入口,不是拿掉 API 能力。


真正的授權要在 Backend 再檢查一次

假設 API:

PATCH /orders/123

Backend 收到 Request 後,

不能只相信:

Frontend 既然送來了,代表這個人一定有權限。

而是還要自己確認:

這個 Request 是誰送的?

他的角色是什麼?

他可以修改這筆資料嗎?

例如概念上:

Request
↓
Authentication
↓
知道你是誰
↓
Authorization
↓
確認你可不可以做這件事
↓
允許 / 拒絕

Authentication 和 Authorization 又不一樣

這兩個詞很像,

我以前也很容易混。

可以先這樣記:

Authentication
→ 你是誰?

Authorization
→ 你可以做什麼?

例如:

登入 Google
↓
系統知道:
你是 user@example.com

這比較接近:

Authentication

接著系統判斷:

這個人是 Admin?
Editor?
Viewer?

再決定:

可不可以 Edit
可不可以 Delete
可不可以 Approve

這才是:

Authorization

所以完整流程應該有兩層

例如編輯功能:

Frontend
↓
canEdit = true
↓
顯示 Edit Button
↓
使用者點擊
↓
PATCH Request
↓
Backend 再檢查權限
↓
允許更新

如果使用者沒有權限:

Frontend
↓
不顯示 Button

就算他真的想辦法自己送:

PATCH Request

Backend 仍然應該:

拒絕

這才比較完整。


Frontend Permission 和 Backend Authorization 各自負責什麼?

可以先這樣分:

Frontend

負責:

使用者看得到什麼?
哪些操作要隱藏?
哪些欄位 disabled?
哪些頁面不該出現在 UI?

主要目的:

UX
+
避免誤操作

Backend

負責:

這個 Request 到底允不允許?

主要目的:

Security
+
資料保護

所以它們不是:

二選一

而是:

兩層都要有

一個很危險的想法:反正按鈕藏起來就沒人能按

例如:

{isAdmin && (
  <Button onClick={deleteUser}>
    Delete
  </Button>
)}

如果 Backend 的:

DELETE /users/123

完全沒有驗證使用者權限,

那真正的限制就只存在於:

React UI

這種保護很脆弱。

因為:

API 本身還是接受任何已知 Request

所以不能把:

Frontend condition

當成真正安全控制。


Route Guard 也是一樣

例如:

if (!canView) {
  return <Forbidden />;
}

這可以阻止一般使用者透過 UI 看到頁面。

但如果頁面背後有:

GET /admin/report

Backend 一樣要檢查。

不能因為:

前端沒有 Route

就假設:

API 是安全的。

Permission 其實也可以看成一種資料

例如前端可能拿到:

{
  canView: true,
  canEdit: false,
  canDelete: false
}

Component 再根據:

canEdit

決定:

Button 顯示與否。

所以前端資料流可能是:

User
↓
Role / Permission
↓
Frontend State / Context
↓
Component
↓
UI

這也是為什麼大型專案裡,

權限常常不會每個頁面自己寫:

role === "ADMIN"

而是抽成比較統一的:

Permission
Access
canEdit
canView

為什麼不要到處直接判斷 Role?

例如:

role === "ADMIN"

今天看起來沒問題。

但需求可能變成:

Admin 可以編輯

Manager 在自己部門也可以編輯

Owner 可以編輯自己建立的資料

這時候:

role === "ADMIN"

就不夠了。

真正的問題其實是:

這個人「能不能 Edit」?

所以:

canEdit

通常比:

isAdmin

更貼近 Component 真正需要知道的事情。


這有點像前面講過的資料責任

Component 可能根本不需要知道:

你是 Admin
Manager
Owner

它只需要知道:

<Button disabled={!canEdit}>

也就是:

你告訴我能不能 Edit 就好。

至於:

canEdit 是怎麼算出來的

可以留在更上層的 Permission Logic。


但前端算出的 canEdit 也不能成為 Backend 的依據

例如:

canEdit = true;

這只是前端目前的狀態。

不能送:

{
  "canEdit": true
}

然後 Backend 就相信:

好,你說你可以編輯,那你就可以。

真正的 Backend Authorization,

應該根據:

經過驗證的使用者身份
+
Server 端權限規則
+
目前資源

重新判斷。


所以不要相信 Client 傳來的權限結果

Client 可以傳:

我要修改哪一筆資料
我要做什麼操作

但不應該讓 Client 自己決定:

我有沒有權限。

因為 Client 資料可以被修改。

這也是一個很重要的安全觀念:

不要把 Client 當成可信任環境。


如果前端已經隱藏按鈕,Backend 還要回 403 嗎?

要。

因為 Backend 不知道 Request 是不是一定從正常 UI 來的。

如果使用者沒有權限,

API 應該明確拒絕。

常見可能看到:

401 Unauthorized
403 Forbidden

兩個名字很容易混。

可以先簡單理解:

401
→ 身份驗證有問題
→ 例如還沒登入 / Token 無效

403
→ 我知道你是誰
→ 但你沒有權限做這件事

實際狀態碼仍然要看 API 設計。


Debug 權限問題時,我現在會拆兩層

假設:

使用者看不到 Edit Button。

我會先問:

Frontend 的 canEdit 是多少?
↓
Permission 是怎麼算的?
↓
Button condition 是什麼?

這是:

UI 問題

但如果:

Button 看得到,點擊後 API 403。

那要再查:

使用者身份是誰?
↓
Backend 權限規則是什麼?
↓
這筆 Resource 是否允許這個人修改?

這是:

Authorization 問題。

反過來,如果 Button 不該出現,但 API 其實可以成功呢?

這反而更值得注意。

因為:

Frontend 不讓操作

但:

Backend 卻允許

代表:

UI 看起來有權限控管,但真正的 Security Boundary 可能沒有做好。

這種問題不能只修:

Button condition

還要確認:

Server Authorization

是不是正確。


所以「權限 Bug」也要問 Root Cause 在哪一層

例如:

不能編輯

這句話其實太模糊。

可能是:

Button 不見

可能是:

Button disabled

可能是:

Route 進不去

可能是:

Request 403

可能是:

Backend Business Rule 拒絕

這些看起來都是:

沒權限。

但實際問題層級完全不同。


我現在會把權限畫成這樣

User Identity
↓
Authentication
↓
Permission / Role
↓
┌────────────────────┐
│ Frontend           │
│                    │
│ canView            │
│ canEdit            │
│ canDelete          │
│                    │
│ 控制 UI            │
└────────────────────┘
          ↓
       Request
          ↓
┌────────────────────┐
│ Backend            │
│                    │
│ Authorization      │
│                    │
│ 再次確認是否允許   │
└────────────────────┘
          ↓
       Database

Frontend:

讓介面符合使用者權限。

Backend:

真正保護資源。


這也是 UI/UX 和 Security 很有趣的交界

UI/UX 在意:

不要讓使用者看到不能做的操作

讓狀態清楚

減少挫折

Security 在意:

即使有人繞過 UI
也不能做沒有權限的事情

兩邊目標不同,

但其實應該一起合作。


今天真正想記的是

隱藏 Button
≠
禁止 API

以及:

Authentication
→ 你是誰?

Authorization
→ 你能做什麼?

Frontend Permission 很重要,

但它主要控制:

UI / UX

真正的安全邊界,

還是必須由:

Backend

再檢查一次。

所以之後看到:

{canEdit && <Button />}

我不會只想:

有做權限了。

而會多問一句:

如果有人完全不經過這個 Button,直接打 API,Backend 也會拒絕嗎?

如果答案是:

Yes

那才比較接近完整的權限設計。

下一篇可以繼續把「安全邊界」往前推一步:

只是把 HTML Email Preview 顯示在 iframe 裡,為什麼還要加 sandbox?


上一篇
Day 26|/:id 怎麼把別人的網址吃掉了?Static Route 和 Dynamic Route 到底差在哪?
下一篇
Day 28|只是顯示 HTML,為什麼還要加 iframe sandbox?
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言