Line Login Jwt 驗證簽名 自行實作 c#範例

Line Login Jwt 驗證簽名 自行實作 c#範例

跟Line整合後,經過OAuth第一步驟拿到的grant_code可以再請求 Line Api拿到Open Id協定的id_token

這個是一個標準,從id_token可以解出平台授權的使用者資訊,可能包含廠商的id, 用戶的識別碼

但是若人人都可以拿這個token重新replay使用的話,那就有更大的風險

因此我們需要驗證其合法性,而這個驗證通常都要在server進行,因為我們不可能把密鑰丟到client的browser去嘛

所以還是好好的保留在Server吧。

以下Line有python3的範例∶ https://developers.line.biz/en/docs/line-login/verify-id-token/#write-original-code

今天我試著改寫一下成c#的版本,這個版本主要是驗證簽名是否合法,至於想再驗證payload上的資訊,是否到期(exp、aud)、正確性,可以再自行取出後比對。

註∶若是自行實作的jwt,可以依不用的加簽實作驗證handler,此處為line的演算法sha256為例

using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.Tokens;

var idToken =
    "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FjY2Vzcy5saW5lLm1lIiwic3ViIjoiVTVkZDllZWNjYmUzMTB..略..c3cKg";

var secret = "{請換成你的line channel secret}";

var result =TryValidateToken(idToken);

Console.WriteLine($"token is valid(only check signature): {result}");
Console.ReadKey();


string base64url_decode(string target)
{
    return Base64UrlEncoder.Decode(target);
}

bool check_signature(string key, string target, byte[] signature)
{
    var hmacsha256 = new HMACSHA256();
    hmacsha256.Key = Encoding.UTF8.GetBytes(key);
    var cal_signature = hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(target));
    var signatureHashed = signature;

    return CompareArraysExhaustively(cal_signature, signatureHashed);
}

bool CompareArraysExhaustively(byte[] first, byte[] second)
{
    if (first.Length != second.Length)
    {
        return false;
    }

    bool ret = true;
    for (int i = 0; i < first.Length; i++)
    {
        ret = ret & (first[i] == second[i]);
    }

    return ret;
}


bool TryValidateToken(string token)
{
    if (string.IsNullOrWhiteSpace(token))
    {
        return false;
    }

    var handler = new JwtSecurityTokenHandler();

    try
    {
        var jwt = handler.ReadJwtToken(token); //JWT驗證物件
        if (jwt == null)
        {
            return false;
        }

        var idTokens = token.Split('.');
        var header = idTokens[0];
        var payload = idTokens[1];
        var signature = idTokens[2];

        var header_decoded = base64url_decode(header);
        var payload_decoded = base64url_decode(payload);
        var signature_decoded = Base64UrlEncoder.DecodeBytes(signature);

        var valid_signature = check_signature(secret,
            header + '.' + payload,
            signature_decoded);

        return valid_signature;
        
        //傳統驗證方法 ..
        // ClaimsPrincipal principal = null;
        // var secretBytes = Convert.FromBase64String(secret);
        //
        // var validationParameters = new TokenValidationParameters
        // {
        //     RequireExpirationTime = true,
        //     ValidateIssuer = false,
        //     ValidateAudience = false,
        //     IssuerSigningKey = new SymmetricSecurityKey(secretBytes),
        //
        //     //LifetimeValidator = LifetimeValidator
        //
        //     ClockSkew = TimeSpan.Zero
        // };
        //
        // SecurityToken securityToken;
        // principal = handler.ValidateToken(token, validationParameters, out securityToken);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"msg:{ex.Message}");
        return false;
    }
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *