The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

LINE Login 簽發的 ID Token 如何用 ES256 非對稱加密演算法的公開金鑰驗證

我前陣子在研究 LINE Login 使用的 ES256 非對稱加密演算法,當我透過 OpenId Connect 拿到 ID Token 後,我想拿 LINE Login 提供的 JWKs 金鑰組來驗證其有效性,這篇文章我打算分享如何用 C# 搭配 LINE Login 提供的 JWKs 金鑰組來驗證其有效性!

Cyber Security

ES256HS256 傻傻搞不清楚

這個需求我嘗試了好久都寫不出來,直到昨天才發現,原來是 LINE Login 預設並不會核發 ES256 簽發的 JWT Token,所以沒辦法拿 OpenId Connect 提供的 JWKs 來驗證 ID Token。由於 LINE Login 預設是用 HS256 對稱式加密演算法,需要 LINE 的私密金鑰才能驗證,否則就要呼叫 LINE Login 的 Verify API 才能得知真偽,這比較不是我希望的解決方案。我希望我可以在伺服器端,直接透過下載過的 JWKs 金鑰驗證使用者發來的 ID Token,確保驗證 ID Token 的效能與可靠性。

至於要如何讓 LINE Login 發出 ES256 簽發的 ID Token 呢?你必須要認真看完我的 如何將一個 ASP.NET Core 網站快速加入 LINE Login 功能 (OpenID Connect) 文章,確保從 Token Endpoint 取得 Access Token 與 ID Token 時,要額外送出一個 id_token_key_type=JWK 的參數才行,這真的超雷的!

除此之外,我還發現 HS256 對稱式加密演算法核發的 ID Token,其 JWT Token 的 Header 部分內容如下:

{
  "typ": "JWT",
  "alg": "HS256"
}

而透過 ES256 非對稱式加密演算法核發的 ID Token,其 JWT Token 的 Header 部分內容如下,多了一個 kid (Key ID) 屬性:

{
  "kid": "95e9119f653eae095bb3d871d6ba8ff46467aa314575a4543615f051d4b735a6",
  "typ": "JWT",
  "alg": "ES256"
}

如何驗證 ID Token 的有效性

驗證 ID Token 的有效性主要有三個步驟:

  1. 透過 LINE Login 的 OpenId Connect Discovery Document 得知 jwks_uri 如下:

    https://api.line.me/oauth2/v2.1/certs

    取得內容的程式碼如下,記得要先安裝 IdentityModelSystem.IdentityModel.Tokens.Jwt 這兩個 NuGet 套件:

    var http = new HttpClient();
    
    // dotnet add package IdentityModel
    var disco = await http.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
    {
        Address = "https://access.line.me", // Authority
        Policy = new() { ValidateEndpoints = false }
    });
    
    var certs = await http.GetStringAsync(disco.JwksUri);
    

    這裡的 certs 是一個字串,你可以將該字串快取(Cache)起來,不用一直到 LINE 網站下載憑證!

  2. 取得 JWKs 所有的公開金鑰

    var signingKeys = new JsonWebKeySet(certs).GetSigningKeys();
    

    如果要從 ID Token 的 JWT Header 的 kid 比對出金鑰,可以這樣寫:

    var idToken = "{YOUR_ID_TOKEN}";
    
    var tokenHandler = new JwtSecurityTokenHandler();
    var decodedToken = tokenHandler.ReadJwtToken(idToken);
    //var header    = JsonSerializer.Serialize(decodedToken.Header);
    //var payload   = JsonSerializer.Serialize(decodedToken.Payload);
    //var signature = decodedToken.RawSignature;
    
    var key = signingKeys.FirstOrDefault(k => k.KeyId == decodedToken.Header.Kid);
    
  3. 驗證 ID Token 的真偽

    這裡我就是希望不要透過呼叫 API 的方式驗證 ID Token 的有效性,直接在本機完成驗證動作,執行效能肯定是最好的!

    var handler = new JsonWebTokenHandler();
    TokenValidationResult result = handler.ValidateToken(idToken, new TokenValidationParameters
    {
        ValidIssuer = "https://access.line.me",
        ValidAudience = "1566379427", // LINE Login 的 Channel ID
        IssuerSigningKey = key,
    });
    
    if (result.IsValid)
    {
        Console.WriteLine("The ID Token is VALID.");
    }
    else
    {
        // https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/wiki/PII
        Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
    
        throw result.Exception;
    }
    

總結

這段程式碼我寫好幾個月了,但一直執行失敗,無法驗證,昨天才得知原來是 LINE Login 預設並不會用 ES256 簽發的 ID Token,而 ES256HS256 我又不太熟悉,兩個名字又這麼像,實在很容易搞混!😒

完整實作我放在 https://github.com/doggy8088/LINELoginIDTokenValidator

相關連結

留言評論