# 漫談身份驗證實作 ## 前言 由於 http 是一個無狀態 (stateless) 的協定,我們需要一套機制來驗證使用者的身份及其登入狀態。 目前常見的兩大身份驗證機制包含傳統的 Session-Based Authentication 以及目前主流的 Token-Based Authentication。 假設有座會員制電影院,只有會員才可以進去,不同級別的會員能進入的影廳不一樣... ![](https://i.imgur.com/wAgPArh.png) ## Session-Based Authentication 假設有座會員制電影院,只有會員才可以進去,不同級別的會員能進入的影廳不一樣... Session-Based Authentication 就是有一張會員狀態表,當成功確認會員身份要讓他進入電影院時,便在會員狀態表上更新會員狀態,表上列出所有會員目前是否已經入場的狀態,以及會員的級別、可進入的影廳。每有會員欲進入影廳的門都需要檢查那一張會員狀態表。 ### Session-based 驗證流程 - stateful ![](https://i.imgur.com/KS082xd.png) ## Token-Based Authentication 假設有座會員制電影院,只有會員才可以進去,不同級別的會員能進入的影廳不一樣... Token-Based Authentication 就是當成功確認會員身份要讓他進入電影院時,就發給一張會員卡,出示會員卡就可以進入電影院,會員卡上面會寫上該會員可以進入的影廳。館方不知道有誰在館內,也沒辦法得知特定會員的狀態,但可以省去每次檢查、更新會員狀態表的工。 ### Token-based 驗證流程 ![](https://i.imgur.com/I64jgmR.png) 每次 request 需要帶在 Authorization Header 例如:Authorization: Bearer QWxhZGRpbjpvcGVuIHNlc2FtZQ== #### 怎麼登出? 因為不知道使用者狀態,所以沒辦法用刪除 session 的方式,常見的做法是準備一個黑名單,一但登出的 access token/refresh token 就加到黑名單。 ### JWT (JSON web token) - prononced as "jot" ![](https://i.imgur.com/l4P3oM8.png) 由兩個點分隔成三個區塊,由 A~Z a~z 0~9 - _ 組成 開頭為 eyj 可以明顯知道他是encode 的 JSON 因為 eyj = base64( {" ) #### Header ```json { "alg": "HS256", // hashing algorithm "typ": "JWT", // type of token "cty": // content type } ``` #### Payload(Claims) ```json { "iss":"Wavinfo", // issuer, 發證者 "sub":"12345678", // subject:主體 "aud":"", // Audience:對象 "exp":~~((new Date((new Date()).getTime() + 1000*60*60*24)).getTime() / 1000), // Expiration Time:有效期限 "iat":~~(new Date()), // Issued At:簽發時間 "nbf": ~~((new Date()).getTime() / 1000), // Not Before:生效時間 "jti":"" //JWT ID } ``` 在 JWT token 的 payload (claims) 中通常會包含可以辨認使用者的資訊,也可放入客製化資訊。 - 不要把敏感的資料放在 token 中 - token 中不能放太多東西,會影響簽章、驗證與傳輸的效率 - 只在安全的管道傳遞 token(https) #### Signature 每一個 JWT token 都應該在送出給 client 前進行簽章(sign),簽章是由 payload hash 而來,如果一個 token 沒有簽章,那麼 client 即可自由修改 token 中的內容。 ### 後端如何使用 JWT #### 產生一個 JWT 例如:[node-jsonwebtoken](https://github.com/auth0/node-jsonwebtoken), aws cognito ```javascript jwt.sign(payload, secretOrPrivateKey, [options, callback]) ``` 送出到 client 端前,需用一把私鑰將 JWT 進行簽章以進行後續驗證。 #### 驗證回來的 JWT 將取回的 token 的 Header & payload 用原有的私鑰再進行一次 hash ,確認是否所得的 hash 結果和 token 中的 Signature 相同,即可驗證 token 是否為當初發出的,且可確認 payload 內容並未經過竄改。 #### Access Token - 用於獲得授權存取資源 - 效期短暫 #### Refresh Token - 用於取得 access token - 效期較長 #### Refresh API 用於以 refresh-token 交換 access-token [RFC 6749 - OAuth2.0](https://www.rfc-editor.org/rfc/rfc6749#section-1.4) ## Session-based v.s. Token-based ### 效能 Session-based :因為 session 存在 server 端,每個 request 都需要拿 session_id 去 query 一次 DB 以取得登入狀態,所以當 DB 資料越龐大效能越差 Token-based : 不需等待 DB query ### 易擴充性 Session-based :因為 session 都存在 server 端,資料量只會越來越大,且若有加開機器的情況將會更加麻煩處理。 Token-based : 因為都存在前端所以不會有擴充問題 ### CORS Session-based :基於同源政策要求 client 與 server 必須要設在同一個 domain 或 sub domain,才能順利傳送 cookie,例如 app.example.com的網頁,發送請求給api.example.com的 server,如有需要跨 domain 的請求時 Token -based 會是較好的作法 Token-based : 因為是透過 Header 傳輸 token 所以不會有 CORS 問題 ### 易管控性 Session-based :如果有需要立即修改使用者狀態的情況,較易進行處理。例如需要立即關閉某使用者帳號權限時,僅需要在 DB 修改即可。 Token-based : 因為狀態都儲存在前端,難以管理使用者狀態 ## 潛在的威脅 ### XSS 攻擊 - 偷走你的鑰匙 例如:於 Session-based 的驗證方式中,使用cookie 儲存 session id 的時候,未啟用 Http Only ### CSRF 攻擊 - 讓瀏覽器自己拿鑰匙開門 CSRF 的全名為 Cross Site Request Forgery,中文名為「跨站請求偽造」,也被稱為 one-click attack,當網站設計不良導致可冒用 cookie。 Ref: [OWASP - CSRF](https://owasp.org/www-community/attacks/csrf) 通常為了預防這種攻擊,會需要小心設定 cookie 例如啟用 SameSite、檢查 request 中的 referer,另外一種做法是 csrf token,由前端產生一個隨機碼,將其加密後放到 cookie,並把未加密的隨機碼作為 request 參數放到 url 中傳輸,後端去解密後去確認兩者是否相同。或是由 server 端產生一個隨機碼存在 session 中,並將其加密後傳給前端,前端每次發request 必須帶到 header 或 form 隱藏的欄位中(for form action)。 ![](https://i.imgur.com/1TjBuSW.png) ## Token-based 前端實作 ### 前端應該把 token 存在哪裡才是安全的?? ![](https://i.imgur.com/k4UQ6tE.png) * Local storage ? * Session storage ? * Cookie ? ### 降低風險的常見做法 #### Access token 放到無法輕易取得的地方 以下各別談談可能的儲存位置及其可能的風險 * Local storage/Session storage: 有 XSS 風險,除非你的 token 不是駭客會有興趣的,不然不要放在這裡,但也有一種說法是說可以放在這裡並加上 Content Security Policy 來預防 XSS * HttpOnly Cookies: 有可能會有 CSRF 風險,且容量僅有 4kb 沒辦法存太多資訊 * Closures: 存在記憶體中,駭客較不易取得,但因為前端的程式是公開的,還是有可能被取走 * Web workers: 為 JS 創造 multi tread 的一個 browser API ([MDN - Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers)) 。web workers 執行在另外的一個全域環境,可將其 global reference 儲存在 JS closure 之中,以防範第三方 JS 意圖與 web worker 溝通 ![](https://i.imgur.com/Crkav3D.png) * Service worker / iFrame: 雖然和 web worker 一樣 Service worker 和 iFrame 都和主線程在不同的全域環境底下,但不同點是他們都有 global reference 代表說程式可以輕易取得裡面的資訊 #### Access token 效期越短越好 例如 5~10 分鐘就會過期需要重新取得 #### Refresh Token Rotation 每次打 refresh api 的時候,就把 client 端帶來的 refresh token 加到黑名單,再發新的 refresh token 給他 ## Reference * https://medium.com/%E4%BC%81%E9%B5%9D%E4%B9%9F%E6%87%82%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88/jwt-json-web-token-%E5%8E%9F%E7%90%86%E4%BB%8B%E7%B4%B9-74abfafad7ba * [[note] JWT](https://pjchender.dev/webdev/note-jwt/) * [How to log out when using JWT](https://medium.com/devgorilla/how-to-log-out-when-using-jwt-a8c7823e8a6) * [Session vs Token Based Authentication](https://www.geeksforgeeks.org/session-vs-token-based-authentication/) * [讓我們來談談 CSRF](https://blog.techbridge.cc/2017/02/25/csrf-introduction/) * [Mitigating CSRF attacks in Single Page Applications](https://medium.com/tresorit-engineering/modern-csrf-mitigation-in-single-page-applications-695bcb538eec) * [https://stackoverflow.com/questions/71721867/how-to-securely-implement-authentication-in-single-page-applications-spas-with](https://stackoverflow.com/questions/71721867/how-to-securely-implement-authentication-in-single-page-applications-spas-with) * [Web Worker 經驗分享](https://ithelp.ithome.com.tw/articles/10118851) * [Leveraging Web Workers to Safely Store Access Tokens](https://www.thearmchaircritic.org/alternative-facts/leveraging-web-workers-to-safely-store-access-tokens) ### Reference Videos * [React Register Form with Validation](https://youtu.be/brcHK3P6ChQ) * [React User Login and Authentication with Axios](https://youtu.be/X3qyxo_UTR4) * [React Protected Routes | Role-Based Authorization](https://youtu.be/oUZjO00NkhY) * [React Login Authentication with JWT Access, Refresh Tokens, Cookies and Axios](https://youtu.be/nI8PYZNFtac) * [React Persistent User Login Authentication with JWT Tokens](https://www.youtube.com/watch?v=27KeYk-5vJw&ab_channel=DaveGray)