ILINEService
public interface ILINEService
{
string GenerateAuthUrls(string state, string redirectUrl);
/// <summary>
/// 取得Line Access Token(Line登入資料)
/// <https://developers.line.biz/en/docs/line-login/web/integrate-line-login/#spy-getting-an-access-token>
/// </summary>
/// <param name="code"> 回傳的驗證碼 </param>
/// <param name="redirectUrl"> LINE 回傳的接收網址 </param>
/// <returns> LineLoginToken </returns>
Task<LINELoginResource?> ChallengeToken(string code, string redirectUrl);
/// <summary>
/// 透過 accessToken 取得用戶基本資料
/// </summary>
/// <param name="accessToken"> Access Token </param>
/// <returns> LINEProfile資料 </returns>
Task<LINEUserProfile?> GetLINEProfile(string accessToken);
/// <summary>
/// 透過 idToken取得用戶資料資料
/// </summary>
/// <param name="idToken">id Token</param>
/// <returns>LINEProfile資料</returns>
Task<LINEUserProfile?> GetLINEProfileWithEmail(string idToken);
/// <summary>
/// 透過LINE發送訊息給使用者
/// </summary>
/// <param name="clientId"> clientId </param>
/// <param name="message"> 訊息 </param>
Task PushMessage(string clientId, List<string> message);
}
LINEService
public class LINEService(IHttpClientFactory clientFactory, IOptions<LINESettings> options) : ILINEService
{
private readonly string _channelId = options.Value.ChannelId;
private readonly string _channelSecret = options.Value.ChannelSecret;
private readonly string _channelAccessToken = string.Empty;
private readonly HttpClient _client = clientFactory.CreateClient();
public const string AuthorizationEndpoint = "<https://access.line.me/oauth2/v2.1/authorize>";
public const string TokenEndpoint = "<https://api.line.me/oauth2/v2.1/token>";
public const string UserInformationEndpoint = "<https://api.line.me/v2/profile>";
public const string UserEmailsEndpoint = "<https://api.line.me/oauth2/v2.1/verify>";
public const string PushMessageEndpoint = "<https://api.line.me/v2/bot/message/push>";
public string GenerateAuthUrls(string state, string redirectUrl)
{
var query = HttpUtility.ParseQueryString(string.Empty);
query["scope"] = "openid profile email";
query["response_type"] = "code";
query["state"] = state;
query["redirect_uri"] = redirectUrl;
query["client_id"] = _channelId;
return $"{AuthorizationEndpoint}?{query}";
}
public async Task<LINELoginResource?> ChallengeToken(string code, string redirectUrl)
{
Dictionary<string, string> formData = new()
{
{ "grant_type", "authorization_code" },
{ "code", code },
{ "redirect_uri", redirectUrl },
{ "client_id", _channelId },
{ "client_secret", _channelSecret }
};
var response = await _client.PostAsync(TokenEndpoint, new FormUrlEncodedContent(formData));
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<LINELoginResource>();
}
public async Task<LINEUserProfile?> GetLINEProfile(string accessToken)
{
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _client.GetAsync(UserInformationEndpoint);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<LINEUserProfile>();
}
public async Task<LINEUserProfile?> GetLINEProfileWithEmail(string idToken)
{
Dictionary<string, string> formData = new()
{
{ "id_token", idToken },
{ "client_id", _channelId },
};
var response = await _client.PostAsync(UserEmailsEndpoint, new FormUrlEncodedContent(formData));
response.EnsureSuccessStatusCode();
using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
return new LINEUserProfile()
{
UserId = payload.RootElement.GetString("sub")!,
DisplayName = payload.RootElement.GetString("name")!,
Email = payload.RootElement.GetString("email") ?? string.Empty,
};
}
public async Task PushMessage(string clientId, List<string> message)
{
_client.DefaultRequestHeaders.Add("authorization", $"Bearer {_channelAccessToken}");
var postMessage = new LINEMessage
{
To = clientId,
Messages = message.Select(x => new Message { Text = x, Type = "text" }).ToList()
};
var json = JsonSerializer.Serialize(postMessage);
HttpContent contentPost = new StringContent(json, Encoding.UTF8, "application/json");
await _client.PostAsync(PushMessageEndpoint, contentPost);
}
}
LINELoginResource
public class LINELoginResource
{
/// <summary>
/// Access token. 有效期為30天
/// </summary>
[JsonPropertyName("access_token")]
public string AccessToken { get; set; } = null!;
/// <summary>
/// 訪問到期時間,以秒為單位
/// </summary>
[JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
/// <summary>
/// Token
/// </summary>
[JsonPropertyName("id_token")]
public string IdToken { get; set; } = null!;
/// <summary>
/// 用於獲取新Token。有效期為90天。
/// </summary>
[JsonPropertyName("refresh_token")]
public string RefreshToken { get; set; } = null!;
/// <summary>
/// 用戶授予的權限
/// </summary>
[JsonPropertyName("scope")]
public string Scope { get; set; } = null!;
/// <summary>
/// Bearer
/// </summary>
[JsonPropertyName("token_type")]
public string TokenType { get; set; } = null!;
}
LINEMessage
public class LINEMessage
{
[JsonPropertyName("to")]
public string To { get; set; } = string.Empty;
[JsonPropertyName("messages")]
public List<Message> Messages { get; set; } = [];
}
public class Message
{
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
[JsonPropertyName("text")]
public string Text { get; set; } = string.Empty;
}
LINEUserProfile
public class LINEUserProfile
{
public string UserId { get; set; } = null!;
public string DisplayName { get; set; } = string.Empty;
public string StatusMessage { get; set; } = string.Empty;
public string PictureUrl { get; set; } = string.Empty;
/// <summary>
/// 需透過idToken才能取得Email
/// </summary>
public string Email { get; set; } = string.Empty;
}
LINESettings
public class LINESettings
{
/// <summary>
/// LINE Login ChannelId
/// </summary>
public string ChannelId { get; set; } = null!;
/// <summary>
/// LINE Login ChannelSecret
/// </summary>
public string ChannelSecret { get; set; } = null!;
/// <summary>
/// LINE Message Api ChannelAccessToken
/// </summary>
public string ChannelAccessToken { get; set; } = string.Empty;
}
Program
builder.Services.Configure<LINESettings>(builder.Configuration.GetSection("Authentication:LINE"));
services.AddScoped<ILINEService, LINEService>();