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

第三方登入系列文章的第五篇「在 ASP.NET Core 整合 GitHub 做第三方登入」,GitHub 是全世界最大的程式原始碼管理平台,每個開發者應該都要有一個 GitHub 帳號,如果我們的網站的服務對象是開發者,應該要有一個 GitHub 第三方登入給使用者使用,整個整合的過程,就記錄在這篇文章當中。

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

建立 OAuth App

先讓 GitHub 認識我們的應用程式,在 GitHub 的個人設定裡面的最下面有一個「Developer settings」,點擊進去之後會看到「OAuth Apps」,點擊中間的「Register a new application」來註冊我們的應用程式。

必填的欄位只有三個,分別是:

  1. Application name:應用程式名稱
  2. Homepage URL:應用程式的首頁,這邊我輸入我自己部落格的網址。
  3. Authorization callback URL:接收 authorization code 的 Callback URL,提前先將規劃好的 Web Api 網址填入,不然就隨便先亂填一個,之後再回來改。

最後,按下「Register application」就建立成功了,建立成功之後,我們還要取得「Client ID」以及「Client secrets」,Client ID 就顯示在畫面上,而 Client secrets 則需要按下「Generate a new client secret」來產生。

當 Client secrets 產生了之後,把它跟 Client ID 一起複製下來備用。

準備 Authorization callback URL

下面我們來撰寫 Callback URL 的程式,使用者授權成功之後,GitHub 會把 code 回傳到我們的 Web Api,我們要拿著 code 發送 POST 到 GitHub 交換 Access Token,然後還要拿著 Access Token 再去跟 GitHub 拿使用者授權的資訊,下面是交換 Access Token 時所需要的資料:

  1. Access Token URL:https://github.com/login/oauth/access_token
  2. User Info Url:https://api.github.com/user
  3. client_id:即 Client ID
  4. client_secret:即 Client secrets
  5. code:即我們拿到的 code
  6. redirect_uri:即 Authorization callback URL

在跟 GitHub 拿使用者授權資料的時候,需要加上「User-Agent」 Header,而 User-Agent 的值有就好,下面是程式碼:

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

    var accessToken = await this.ExchangeAccessToken(code);

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

    var userInfo = await this.GetUserInfo(accessToken);

    // TODO: Save AccessToken and UserProfile

    // TODO: User Login

    return this.Redirect("/");
}

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

    var request = new HttpRequestMessage(HttpMethod.Post, "Access Token Url");

    request.Content = new FormUrlEncodedContent(
        new Dictionary<string, string>
        {
            ["client_id"] = "Client ID",
            ["client_secret"] = "Client secrets",
            ["code"] = code,
            ["redirect_uri"] = "Authorization callback URL"
        });

    var response = await client.SendAsync(request);

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

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

    var result = Regex.Matches(content, "([^&=]+)=([^&=]+)").ToDictionary(m => m.Groups[1].Value, m => m.Groups[2].Value);

    return result["access_token"];
}

private async Task<string> GetUserInfo(string accessToken)
{
    var client = this.httpClientFactory.CreateClient();

    var request = new HttpRequestMessage(HttpMethod.Get, "User Info Url");

    request.Headers.Authorization = AuthenticationHeaderValue.Parse($"Bearer {accessToken}");
    request.Headers.Add("User-Agent", "my-chef-my-life");

    var response = await client.SendAsync(request);

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

    return result;
}

授權網址

再來,我們要來兜授權網址,使用者點擊這個授權網址之後就開始登入的流程,GitHub 的授權網址為:https://github.com/login/oauth/authorize,所需參數如下:

  1. client_id:即 Client ID
  2. redirect_uri:即 Authorization callback URL
  3. state:隨機產生的一段唯一的字串,主要是可以用來避免 CSRF(Cross Site Request Forgery)
  4. scope:想請使用者授權給我們的資料範圍,可參考 Scopes for OAuth Apps - GitHub Docs

根據上述的資訊,我兜出的授權網址如下:

https://github.com/login/oauth/authorize?client_id={ClientID}_uri=http%3A%2F%2Flocalhost%3A5101%2Fgithub%2Flogin%2Fcallback&state=12345abcde&response_type=code&scope=user

整個登入的流程大概就像下圖這樣:

使用者在點擊了授權網址,經由 GitHub 授權成功後,我們能順利拿到使用者的資料,就算是整合成功了。

GitHub 果然還是服務開發者的網站,有關於整合 OAuth 的文件寫得非常清楚,整合的過程相當順利,沒有遇到太大的阻礙,以上,整合 GitHub 做為第三方登入的過程就分享給大家,希望對大家有一點幫助。

參考資料

相關資源

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