Node.js RESTful Web API 登入認證令牌範例 for OAuth 2.0 + JWT

詳細解說 OAuth 2.0 的 Password 授權與 JSON Web Token,並結合 Node.js 建置的 RESTful Web API,實作可提供給內部系統使用的登入認證取得令牌與權限控管範例。
OAuth
OAuth (Open Authorization,開放授權) 是一個認證規範的開放標準,主要用於第三方 (如購物網或論壇使用 Facebook 或 Google 等第三方直接登入而無需輸入帳號與密碼) 公開的 API 認證,目前 OAuth 有 1.0 和 2.0 兩個版本,OAuth 2.0 能夠支持各種複雜的認證場景,也是本範例所採用的版本。
Password 授權
OAuth 2.0 授權類型 (Grant Types) 主要有 Password、Authorization Code、Implicit 與 Client Credentials 四種,本範例 Web API 僅提供自有系統使用,不需考慮第三方問題,因此採用使用者 (user) 於登入介面直接輸入帳號與密碼的 Password 授權類型。
Password 是 OAuth 最簡單的授權方式,它僅需一個步驟即可取得訪問 Web API 的令牌 (token)。
請求認證
使用者於登入介面輸入帳號與密碼,來向伺服器 (server) 發送 POST 請求 (request),並指定 application/x-www-form-urlencoded 類型:
POST /oauth/token HTTP/1.1 Host: 192.168.0.200:3000 Content-type: application/x-www-form-urlencoded grant_type=password&username=jacky&password=xxx
名稱 | 說明 |
---|---|
grant_type | 值為 password 是讓伺服器知道使用 Password 授權類型 |
username | 使用者帳號 |
password | 使用者密碼 |
回應 - 成功
伺服器驗證帳號與密碼通過後,伺服器回應 (response) JSON 格式的令牌訊息時,須於 HTTP 首部 (header) 加入 Cache-Control: no-store
和 Pragma: no-cache
,以確保客戶端不會緩存該回應:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
access_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJIUiBTeXN0ZW0gV2ViIEFQSSIsImp0aSI6MSwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTUxMzQ2MzQwLCJleHAiOjE1NTEzNDYzNTB9.OX5G4ZMFW0SWjSY9Bsd8KH7JwjhlOxbDi3gedvscWag"
token_type: "bearer"
expires_in: 1551346350
scope: "admin"
}
名稱 | 說明 | 備註 |
---|---|---|
access_token | 訪問 Web API 的令牌 | 本範例使用 JWT |
token_type | "bearer" 為 RFC 6750 定義的 OAuth 2.0 所用的 token 類型 | |
expires_in | 令牌的有效時間 (UNIX 時間戳) | 客戶端應用程式判斷用 |
scope | 允許訪問應用程式 (本範例為 Web API) 的權限範圍 |
回應 - 不成功
回應錯誤的 JSON 必填名稱 error
與 HTTP 狀態碼對應表如下,還有一個 JSON 可選名稱 error_description
可自行簡短描述錯誤說明:
HTTP/1.1 400 Bad Request Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { error: 'unsupported_grant_type', error_description: '授權類型無法識別,本伺服器僅支持 Password 類型!' }
名稱 | 值 | 說明 | 狀態碼 |
---|---|---|---|
error | invalid_request | 參數缺少、未知或重複 | 400 |
invalid_client | 客戶端身份驗證失敗 | 401 | |
invalid_grant | 授權無效或過期 | 400 | |
unauthorized_client | 無權限 | 400 | |
unsupported_grant_type | 授權類型無法識別 | 400 |
令牌訪問 Web API
每次訪問 Web API 須一併將令牌添加到 HTTP 首部的 authorization,於啟始處先輸入 Beare
在使用一個空格 + 令牌:
GET /accounts HTTP/1.1 Host: 192.168.0.200:3000 authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJIUiBTeXN0ZW0gV2ViIEFQSSIsImp0aSI6MSwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTUxNDQ2NjQzLCJleHAiOjE1NTE0NDc2NDN9.Imd7LKXQA0YPWddNbLW_-CvbiEGsSs8rlmNDLpcr0U0
JWT
JWT (JSON Web Token) 是一個用來產生訪問應用程式 (本範例為 Web API) 令牌的開放標準 (RFC 7519),它能夠加密 JSON 來安全的傳輸訊息。
JWT 常使用在這兩種場景:
- 授權:使用者登入後取得 JWT (搭配 OAuth 2.0 存放於回應 JSON 令牌的
access_token
屬性),後續每個訪問 Web API 的請求必須包括 JWT,伺服器會驗證令牌是否合法,也就是沒被篡改。 - 訊息交換:JWT 能夠驗證簽名的 JSON 訊息是否被篡改,因此 JSON 訊息能安全的被傳輸。
數據結構
JWT 由符號 .
分隔三個部份所組成:
- Header (頭部)
- Payload (負載)
- Signature (簽名)
┌──────────────────────────────── Payload ───────────────────────────────┐ │ │ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c │ │ │ │ └────────────── Header ────────────┘ └─────────────── Signature ───────────────┘
Header
Header 是一個 JSON Object,由兩個名稱組成:
- alg:使用的簽名演算法,預設值為 HMAC SHA256 (HS256)。
- type:令牌的類型,這裡使用的是 JWT。
Node.js 範例程式:
Payload
Payload 是一個 JSON Object,用來存放實際要傳輸的數據,有三種類型。
- Registered claims (註冊聲名):JWT 標準已註冊的聲名 (非必須,但建議使用)。
- Public claims (公開聲名):自訂的聲名。
- Private claims (私人聲名):本範例未使用。
聲名 | 說明 |
---|---|
iss (issuer,發行人) | 誰申請的 JWT (可填入使用者名稱) |
exp (expiration time,到期時間) | JWT 的到期時間 (UNIX 時間戳) |
sub (subject,主題) | JWT 的主題 (如 HR Web API) |
aud (audience,受眾) | 本範例未使用 |
nbf (Not Before,生效時間) | |
iat (Issued At,簽發時間) | |
jti (JWT ID,JWT 編號) |
以下為有效的 Payload,使用到兩個 JWT 標準聲名 "sub" 和 "iat",以及一個自訂聲名 "name":
Node.js 範例程式:
Signature
Signature 部份則使用 Header 中名稱 "alg" 指定的演算法 HS256,對下列三項進行加密產生:
- Base64 URL 編碼的 Header。
- Base64 URL 編碼的 Payload。
- 自訂的 secret (私鑰)。
由於 Signature 使用 secret 方式加密,因此擁有 secret 的伺服器可 verify (驗證) 訊息是否被篡改。
Node.js 程式碼:
產生 JWT
將上述 Header、Payload 和 Signature 各自產生的結果使用符號 .
串接即為 JWT。
Node.js 程式碼:
線上調試器
JWT 官網提供一個線上調試器 (Debugger),可自訂所有數據並直接產生 JWT,非常方便在初期架設服務時的測試驗證。
Node.js 實作範例
模組
範例依賴的模組:
- body-parser - npm:解析 HTTP 請求主體的中介軟體。
- cors - npm:跨來源資源共用 (允許不同網域的 HTTP 請求)。
- express - npm:快速建置 Web 應用程式架構。
- jsonwebtoken - npm:JWT 簽名和驗證。
- mysql - npm:連結 MySQL。
程式架構
webapi/ (專案目錄) │ ├── models/ (程式業務邏輯與資料庫存取) │ │ │ ├── accounts.js │ └── oauth2.js (OAuth 2.0、JWT 和相關驗證功能) │ ├── routes/ (負責轉發請求並回應結果) │ │ │ ├── accounts.js │ ├── oauth2-token.js (OAuth 2.0 請求認證與回應令牌) │ └── token-verify.js (token 驗證) │ ├── app.js (應用程式進入點) ├── conf.js (設定檔) └── functions.js (自訂 function)
程式碼
設定檔
conf.js:
自訂 function
functions.js:
應用程式進入點
app.js:
routes
accounts.js:
oauth2-token.js:
token-verify.js:
models
accounts.js:
oauth2.js:
下載範例
下載範例後的建立步驟如下:
- 至 MySQL 執行範例內的檔案 accounts.sql (已建立的兩個帳號 password 同 username)。
- 編輯設定檔修改資料庫設定。
- 將範例內的目錄 webapi 放置可運行 Node.js 的環境,執行
npm install
安裝所有依賴模組。
測試
啟動應用程式:
使用 Chrome 擴充功能應用程式 Advanced REST client 進行測試。
請求認證
HTTP 頭部指定 application/x-www-form-urlencoded 類型:

輸入請求參數點擊 SEND,驗證成功會回應 JSON,複製最下方回應的 JWT:

令牌訪問 Web API
指定 HTTP 首部的 authorization,於啟始處先輸入 Beare
在使用一個空格 + 貼上上述剛複製的 JWT 後點擊 SEND,即可取得請求 Web API 的資源:

參考

本著作係採用創用 CC 姓名標示-相同方式分享 3.0 台灣 授權條款授權.