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;
}
}