JWT 應用

在使用 JWT 時,必須得先了解它的特性,才不會踩到禁區或是誤解 JWT 的目的。

就結果來說,JWT 在描述一個 token 的長相,以及在資訊充足的情況下要如何驗證這個 token。而怎麼發 token、怎麼做身分驗證、token 該如何保存、token 該如何失效,這些都不是 JWT 在意的。

JWT 讓 token 帶有身分驗證或額外的資訊,而簽章或加密的目的則是為了要安全地交換訊息。因此通常 JWT 不會單獨使用,而是會配合某個特定流程。

身分驗證

因 JWT 配合適合的演算法,可以確保 token 的來源以及資料完整性。比方說配合 ES256 演算法,以及發行者(issuer)與接收者(audience)預先準備好分配好私鑰與公鑰,如此一來,接收者可以用該發行者所產生的 JWT 來做為身分驗證的憑證(credentials)。

事實上,這正是 Sign in with Apple 用來產生 Client Secret 的方法。它的 Claim 如下:

$claims = [
'iss' => $teamId,
'iat' => time(),
'exp' => time() + 300,
'aud' => 'https://appleid.apple.com',
'sub' => $clientId,
];

詳細實作可參考:What the Heck is Sign In with Apple?

接著用此 $claim 加上 ES256 演算法簽章,即可產生一個 JWT。這就能做為 AppleID 認證時所要帶的 Client Secret 參數。

上述身分驗證是以 JWT 做為憑證來使用,而 OpenID Connect 則是身分驗證完之後,產生 JWT 來做為驗證通過的憑證。未來會再說明 OpenID Connect 的細節。

OAuth 2.0 直接發 JWT

JWT 只管 token 的長相,而不管流程;而 OAuth 2.0 則是定義了授權流程,而不管 token 的長相,因此兩個剛好可以組合在一起使用,這就是 JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens 所在討論的。

這個方法要思考幾個問題:

  1. 它有特別定義 typ 參數為 at+JWT,理論上應該是驗證此 JWT 的內容流程跟一般 JWT 不同,如這裡有額外定義 claim client_id 是必要的
  2. 規範仍為 draft,因此流程是有可能會再調整的
  3. JWT 該如何主動讓它失效?

尤其第三點是使用 JWT 很容易會忽略的問題,這問題在未來討論安全性議題會聊聊。

替代方案

有些人會質疑 JWT 的安全性,最主要的原因是,演算法明確的夾帶在 token 裡面,所以認為這是有風險的。而預定義演算法目前都有符合 Kerckhoffs’s principle,只是電腦運算能力越來越強,目前安全不代表未來還會是安全的,如 SHA-1 過去被認為可以取代 MD5,但最後還是被 Google 成功破解; SHA-2 雖然還沒有發現破解方法,但 NSA 還是繼續研發 SHA-3 防範未來。只是,若要這麼思考的話,就沒有任何系統是絕對安全的,包含自幹演算法也是(參考莫非定律),因此最終也只是看要如何選擇。

如果是因為演算法公開的原因,而不想用 JWT 的話,首先一個選擇是:自定義演算法。看完 JWA 的註冊表應該可以了解,它其實也是能自定義的,這也算是 RFC 上面很多規範的特色。比方說可以定義一個這樣的演算法:

{
"alg": "MS2048"
}

然後傳送與接受定義好演算法的簽章或加密方法即可。以此為例,攻擊者看到 MS 也許會以為是微軟發布的演算法呢!

若自定義演算法覺得不行的話,那也只能使用其他 token 格式,比方說 Paseto,網站裡甚至能找到得為什麼不要使用 JWT 的理由。但事實上,Paseto 的規範是在 2018 年發布的,也還在 draft 階段,理論上也還沒有足夠的測試可以確保其安全性。

筆者個人建議使用相較穩定的 JWT,但若覺得 Paseto 的理由有理,亦可以改用 Paseto。

參考資料