LINE登入-手動建立登入 OAuth 2
Line 手動建立OAuth 和 Facebook及Google並沒有不同,都是標準的OAuth流程,有差異的地方在於Line Profile 並沒有提供Email,相反的在取出AccessToken時,也會同時取得IdToken,就看Email是否是必要項目。這邊分兩部分,上半部是申請應用程式服務,下半部是手動建立OAuth程式碼,如果建立應用程式服務的畫面與你現在的不同,不要太意外,可能改版了。
Note
要特別留意的是,若決定使用第三方驗證的Id作為會員Primary key,則服務不可隨意中止,因為不同的Provider取得的UserId是不一樣的。
例如:同時申請兩種不同的服務,LINE Login、Line Bot,兩者皆可取得UserId,但同一使用者在這兩者服務中是不同UserId的。
建立應用程式服務
-
到 Line Developer API 登入建立應用程式
-
選擇Create a LINE Login channel
-
填入Channel name和Channel Description,有提醒你哪些欄位不能為空
-
完成後你應該就可以看到Channel ID 和 Channel secret,這兩欄我們在交換權杖的時候會用到
-
下面有一個Email address permission,這個是選填,勾選後,使用者在第一次登入時,會出現是否同意讓應用程式讀取Email,如果你的應用程式不需要Email就不用管他
-
接下來在LINE Login的地方,要填寫回傳網址,填寫完畢後,整個建立過程就結束了
實作
-
取得叫用「登入」對話方塊與設定重新導向網址
這邊參數請參考https://developers.line.biz/zh-hant/docs/line-login/integrate-line-login/#making-an-authorization-request
1
2
3
4
5
6
7
8
9
10
11
|
public IActionResult Index()
{
State = Guid.NewGuid();
ViewData["LineAuth"] = $"https://access.line.me/oauth2/v2.1/authorize?" +
$"client_id={_appId}" +
$"&response_type=code" +
$"&redirect_uri={RedirectUrl}" +
$"&scope={HttpUtility.UrlEncode("profile openid email")}" +
$"&state={State}";
return View();
}
|
-
前台簡單建立一個連結按鈕
1
|
<a href="@ViewData["LineAuth"]" class="btn btn-primary" title="Log in your account">Line</a>
|
-
點了會出現登入介面
-
使用者輸入完後,會導回我們剛剛設定的回傳網址
-
傳送使用代碼交換存取權杖,完整程式碼放在後面
留意一下官網告知的傳送格式,是使用 application/x-www-form-urlencoded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 使用代碼交換存取權杖 與Facebook 和 Google不同,是使用 application/x-www-form-urlencoded
var url = "https://api.line.me/oauth2/v2.1/token";
var postData = new Dictionary<string, string>()
{
{"client_id",_appId},
{"client_secret",_appSecret},
{"code",code},
{"grant_type","authorization_code"},
{"redirect_uri",RedirectUrl}
};
var contentPost = new FormUrlEncodedContent(postData);
var client = _clientFactory.CreateClient();
var response = await client.PostAsync(url, contentPost);
|
-
Line API 可以同時取得ClientId Token
和 Access Token
,所以這邊有兩種選擇
-
使用JWT解析Id Token, Nuget > System.IdentityModel.Tokens.Jwt
-
使用 profile API,但很遺憾,這方法不能取得Email
完整程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
public class LoginController : Controller
{
// 這邊要改你申請的應用程式編號及密鑰
private readonly string _appId = "{應用程式編號}";
private readonly string _appSecret = "{應用程式密鑰}";
private string RedirectUrl => "https://" + HttpContext.Request.Host.ToString() + "/Login/SignInLine";
private readonly IHttpClientFactory _clientFactory;
public LoginController(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
[TempData]
public Guid State { get; set; }
public IActionResult Index()
{
State = Guid.NewGuid();
ViewData["LineAuth"] = $"https://access.line.me/oauth2/v2.1/authorize?" +
$"client_id={_appId}" +
$"&response_type=code" +
$"&redirect_uri={RedirectUrl}" +
$"&scope={HttpUtility.UrlEncode("profile openid email")}" +
$"&state={State}";
return View();
}
public async Task<IActionResult> SignInLine(string code, Guid state, string error, string error_description)
{
// 有錯誤訊息(未授權等)、State遺失、State不相同、沒有code
if (!string.IsNullOrEmpty(error) || state == null || State != state || string.IsNullOrEmpty(code))
return RedirectToAction(nameof(Index));
// 使用代碼交換存取權杖 與Facebook 和 Google不同,是使用 application/x-www-form-urlencoded
var url = "https://api.line.me/oauth2/v2.1/token";
var postData = new Dictionary<string, string>()
{
{"client_id",_appId},
{"client_secret",_appSecret},
{"code",code},
{"grant_type","authorization_code"},
{"redirect_uri",RedirectUrl}
};
var contentPost = new FormUrlEncodedContent(postData);
var client = _clientFactory.CreateClient();
var response = await client.PostAsync(url, contentPost);
string responseContent;
if (response.IsSuccessStatusCode)
responseContent = await response.Content.ReadAsStringAsync();
else
return RedirectToAction(nameof(Index));
var lineLoginResource = JsonConvert.DeserializeObject<LINELoginResource>(responseContent);
// 因為Line API 可以同時取得ClientId Token 和 Access Token,所以這邊有兩種選擇
// 1. 使用JWT解析Id Token, Nuget > System.IdentityModel.Tokens.Jwt
// var userInfo = new JwtSecurityToken(lineLoginResource.IDToken).Payload;
// 2. https://developers.line.biz/en/reference/social-api/#profile
url = $"https://api.line.me/v2/profile";
client.DefaultRequestHeaders.Add("authorization", $"Bearer {lineLoginResource.AccessToken}");
response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
responseContent = await response.Content.ReadAsStringAsync();
var user = JsonConvert.DeserializeObject<LINEUser>(responseContent);
}
return View();
}
public class LINELoginResource
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public string ExpiresIn { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
// 這邊跟一般的TokenResponse不同,多了使用者的Id Token
[JsonProperty("id_token")]
public string IDToken { get; set; }
}
public class LINEUser
{
[JsonProperty("userId")]
public string Id { get; set; }
[JsonProperty("displayName")]
public string Name { get; set; }
[JsonProperty("pictureUrl")]
public string PictureUrl { get; set; }
[JsonProperty("statusMessage")]
public string StatusMessage { get; set; }
}
}
|
參考
Line Developer
https://developers.line.biz/zh-hant/
整合 LINE Login 與 web app
https://developers.line.biz/zh-hant/docs/line-login/integrate-line-login/#設定-channel
LINE Profile API
https://developers.line.biz/zh-hant/reference/social-api/#get-user-profile
結論
Line是繼Google和Facebook登入後才整理的,一開始覺得Facebook和Google,有提供SDK很方便,貼上就可以動了,但其實到這邊,我覺得手動建立OAuth感覺更簡單,流程都相同(複製貼上就三篇…),就只有些微的地方是不同,而且自己可控的範圍提高了,完全可以把這些東西抽象化,只要第三方驗證是採用OAuth方式,作法都是一樣的,也不用太在乎官網如何改版,讓人找不到文件說明…考慮到Line沒有提供SDK,以及需要Email的情境下,若同時要有Facebook、Google、Line登入,這作法成為我最後的選擇。