[料理佳餚] 在 ASP.NET Core 整合 LINE Login 做為網站的第三方登入

第三方登入不是什麼新鮮事,Google、Facebook、Microsoft、...等各大平台也都有提供第三方登入,雖然整合方式看起來都一樣,但是其中有一些眉眉角角還真的要實際整合過才會知道,下面是我用 ASP.NET Core 整合台灣人最多人使用的通訊軟體 - LINE 做為第三方登入的過程記錄。

其他的第三方登入解決方案可以參考下面的連結:

註冊 Channel

我們是用 OAuth 2.0 來跟 LINE 做第三方登入的整合,第一件事是我們要到 LINE Developers 註冊一個 Channel,進入 LINE Developers Console 之後應該會看到下面這個畫面,點擊「Create a LINE Login channel」就能跳到註冊畫面。

其中「Region to provide the service」請選擇「Taiwan」,避免整合的過程中出現一些問題,「Channel name」除了 4-byte Unicode 不能用之外,它還限制 "LINE" 或相似的字不能用,「App types」請勾選「Web app」,其他的欄位就按照需要填一填,最後點擊「Create」。

另外,如果我們希望使用者在用 LINE 登入我們的應用程式的時候,能夠提供 Email,我們必須要在 Channel 的「Basic settings」申請「Email address permission」。

把要打勾的地方都打勾,比較特別的是,它要我們提供一張應用程式中有關使用者同意提供 Email 的畫面截圖,這邊我是隨便丟一張我個人的頭像上去,最後按下「Submit」,居然申請通過了。

準備 Callback URL

下一步我們要準備一個 Callback URL 來串接 LINE Login OAuth 的流程,我用 ASP.NET Core MVC 建立了一個專案,撰寫了一個 /line/login/callback 的 Web Api,根據 LINE 官方的文件 - Receiving the authorization code 的說明,我們會收到這些資訊:codestatefriendship_status_changedliffClientIdliffRedirectUri,其中對我們比較重要的是 code 這個資訊,我們要拿它來跟 LINE 交換 Access Token

拿到 LINE 給我們的資料之後,再根據另一份 LINE 官方文件 - Issue access token,我們準備好下列的資料及對應的值,組成一個 POST 發送給 LINE,跟它要 Access Token 以及使用者授權的資料。

  1. grant_type:填入 "authorization_code"
  2. code:即我們拿到的 code
  3. redirect_uri:即 Callback URL
  4. client_id:即 Channel ID
  5. client_secret:即 Channel Secret

在拿到使用者授權的資料之後,後續就是要將資料存下來,並且自動將使用者登入到我們的應用程式,這兩個部分就留給各位朋友自行發揮了。

[HttpGet("/line/login/callback")]
public async Task<IActionResult> Callback()
{
    if (!this.Request.Query.TryGetValue("code", out var code))
    {
        return this.StatusCode(400);
    }

    var (accessToken, idToken) = await this.ExchangeAccessToken(code);

    if (accessToken == null)
    {
        return this.StatusCode(400);
    }

    // TODO: Save AccessToken and IdToken

    // TODO: User Login

    return this.Redirect("/");
}

private async Task<(string, string)> ExchangeAccessToken(string code)
{
    var client = this.httpClientFactory.CreateClient();

    var request = new HttpRequestMessage(HttpMethod.Post, "AccessTokenUrl");

    request.Content = new FormUrlEncodedContent(
        new Dictionary<string, string>
        {
            ["grant_type"] = "authorization_code",
            ["code"] = code,
            ["redirect_uri"] = "CallbackUrl",
            ["client_id"] = "ChannelId",
            ["client_secret"] = "ChannelSecret".
        });

    var response = await client.SendAsync(request);

    if (response.StatusCode != HttpStatusCode.OK)
    {
        return (null, null);
    }

    var content = await response.Content.ReadAsStringAsync();

    var result = JsonNode.Parse(content);

    var accessToken = result["access_token"].GetValue<string>();
    var idToken = result["id_token"].GetValue<string>();

    return (accessToken, idToken);
}

更新 Callback URL

我們準備好 Callback URL 之後,就到 LINE Login 的管理頁面,在我們的 Channel 設定畫面有一個「LINE Login」頁籤,底下有一個「Callback URL」的欄位,點擊「Edit」進行編輯。

Callback URL 填好後,按下「Update」就可以了,在開發期間,網址可以允許用 localhost 沒有關係,如果之後要對外開放的話,還是得要去弄個公開的網址。

授權網址

接下來就只差最後一步了,我們只要兜出授權網址,基本上就大功告成了,而授權網址哪裡來?根據 LINE 官方文件 - Authenticating users and making authorization requests 的說明,我們可以知道是 https://access.line.me/oauth2/v2.1/authorize,必填的參數為:

  1. response_type:填入 "code"
  2. client_id:即 Channel ID
  3. redirect_uri:即 Callback URL
  4. state:隨機產生的一段唯一的字串,主要是可以用來避免 CSRF(Cross Site Request Forgery)
  5. scope:想請使用者授權給我們的資料範圍

配合這些資訊,最終會兜出下面這樣的網址:

https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id={ClientId}&redirect_uri=http%3A%2F%2Flocalhost%3A5101%2Fline%2Flogin%2Fcallback&state=abcde123&scope=profile%20openid%20email

將這個網址丟給使用者,使用者點下去之後就開始進入登入的流程,整個登入的流程大概是像這樣。

使用者透過 LINE 所提供的登入畫面,輸入帳號密碼驗證通過之後,就會看到授權畫面,確認沒問題按下「許可」後,我們就能在 Callback URL 拿到 code,再用 code 去跟 LINE 交換 Access Token 及使用者授權的資料,這樣就算是整合成功了。

更新使用者資料

我們從 LINE 取得使用者授權的資料之後,通常來講,會在自己的資料庫存一份起來,方便我們來顯示使用者的資訊,可是使用者可能在 LINE 會更新他自己的資料,像是暱稱、頭像、...等,那我們怎麼來更新?

像這種實作了 OIDC(OpenID Connect) 標準的平台,通常都會有 UserInfo 的端點,從 LINE 的官方文件 - Get user information 之中,我們也可以看到 LINE Login 的 UserInfo 端點為 https://api.line.me/oauth2/v2.1/userinfo,所以我們只要把 Access Token 帶上,發個 GET 過去,我們就能拿到使用者的資料。

[HttpGet("user-info")]
public async Task<IActionResult> UserInfo()
{
    var userId = "從登入資訊取得 User Id";

    var accessToken = "用 User Id 到資料庫撈 Access Token";

    var client = this.httpClientFactory.CreateClient();

    var request = new HttpRequestMessage(HttpMethod.Get, "UserInfoUrl");

    request.Headers.Authorization = AuthenticationHeaderValue.Parse($"Bearer {accessToken}");

    var response = await client.SendAsync(request);

    var userInfo = await response.Content.ReadAsStringAsync();

    return this.Json(userInfo, "application/json");
}

發佈 Channel

最後的最後,我們終於把整合 LINE 第三方登入給開發好了,在公開給使用者使用之前,我們還需要將 Channel 給「Publish」,並且將 Callback URL 從開發用的網址給改掉。

最終,我們 Channel 的狀態應該會是 Published,而 Callback URL 也被修改成使用者能存取得到的網址。

第三方登入對應用程式的使用者而言有其便利性,對應用程式本身來講,把驗證使用者身分這個責任推給其他平台,在資安上的負擔也會減輕一些,甚至說有些服務連註冊帳號的功能都沒有,只提供第三方登入,所以現代的應用程式有第三方登入幾乎是不可或缺的,以上,整合 LINE 第三方登入的流程就分享給大家,希望對大家有一點幫助,都能整合成功。

參考資料

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學