自訂.NET Core Identity身份驗證和授權教學:Google 登入 自訂.NET Core Identity身份驗證和授權教學:Google 登入

Published on Saturday, April 22, 2023

.NET Core Identity Google External Provider

今天來學習如何在 .Net 中使用第三方登入, 我們之前在 Github 上面了解 微軟提供了 GoogleTwitterFacebook 等外部登入驗證

在使用之前需要先到Google API & Services 申請一組 Client IDClient Secret ,詳細可以參考 微軟文檔 需要注意填入 redirect URI 為 https://localhost:{PORT}/signin-google 其中 {PORT} 為自己專案的端口,signin-google 為預設路徑可以不用修改

接下來在我們專案開啟 UserSecrets 功能,並且添加我們剛剛保存的 Id 跟 Secret

dotnet user-secrets init

dotnet user-secrets set "Authentication:Google:ClientId" "<client-id>"
dotnet user-secrets set "Authentication:Google:ClientSecret" "<client-secret>"

完成後回到我們的專案添加新的 Google Package

dotnet add package Microsoft.AspNetCore.Authentication.Google

並且到 Program.cs 加入新的 Google 驗證

builder.Services
    .AddAuthentication(IdentityConstants.ApplicationScheme)
    .AddCookie(IdentityConstants.ApplicationScheme,options =>
    {
        options.ExpireTimeSpan = TimeSpan.FromSeconds(10);
    }).AddGoogle(IdentityConstants.ExternalScheme,o =>
    {
        o.ClientId = configuration["Authentication:Google:ClientId"];
        o.ClientSecret = configuration["Authentication:Google:ClientSecret"];
    });;

最後到 AccountController.cs 新增一個 Controller

[HttpGet(template: "~/externalSignin", Name = "ExternalSignin")]
public async Task ExternalSignin()
{
    await HttpContext.ChallengeAsync(IdentityConstants.ExternalScheme,
        new AuthenticationProperties()
        {
            RedirectUri = "/swagger/index.html"
        });
}

完成之後運行專案發送請求之後發現 Response Headers 裡的 location 會帶入 Google 登入的連結

https://accounts.google.com/o/oauth2/v2/auth
    ?response_type=code
    &client_id=4346799237-7d033l9p89o0rjavo0j2blvk3k7eqltg.apps.googleusercontent.com
    &redirect_uri=https://localhost:7011/signin-google
    &scope=openid profile email
    &state=CfDJ8N83xunRSMFEnHYjbQSeE4ThIgXpHDiNr336vaI0A2XGr9OV_MvVQVuyB8JMzrcWdeTRLZXsWKvgu6C6ReIwWXTKvpzZjUcd_sFNVsyoDy9RosuoHu_gy4R4_SvXvN6gi3KC190ySBQ7Nv4CL1idpD30BqKFSgOgSPQnqk1MoAfdYFeCshQfwuOs8ae-HgSkyyA7Kt-OXTqHNfn5XVtmLLGdCL122B5295qYqY2S_eDL

我們可以手動在瀏覽器開啟此連結,瀏覽器會跳出我們經常使用的 Google 登入,並且要求使用者允許提供資料 按下同意之後 Google 會根據一開始設定的 redirect URI 進行轉跳回到我們的專案

https://localhost:7011/signin-google
    ?state=CfDJ8N83xunRSMFEnHYjbQSeE4RycERh7J02o8Qr90pBqpvCZIirwM5OpIWQbs0ao3Uz1NMD3nSpL_QAhwBI3DAD414_XYmzAPCNEpaNoY_cMCC8C0JVNOzccry8N-l6r307VjXLfO75_XGYY-iMn3WffbYb6KjeTh6KdGAX42PSN0taT_NC7oROcMI5WRs1wsEbMtd8osvpsIAWMfMldS3wo7xMKL4OIporbl6UrFayVy3M
    &code=8/2B69qvKQkKtbceUNphCzkYa6BS7y3NVM-PgDBy9ckf3dKsvST2KfWNqRVGwCZntRtFbDWBM
    &scope=email+profile+openid+https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile
    &authuser=0
    &prompt=none

同時專案 GoogleHandler 會運行 CreateTicketAsync 方法並處理剩下的流程 GoogleHandler.cs
這段流程中最重要的是 Google 會傳給你 AccessToken 也叫做 Authentication Code,程式接收到之後會再拿這組 AccessToken 去請求 Google 的資源伺服器 最後 Google 資源伺服器驗證成功之後才會給我們使用者的私人資料

到此我們可以得知上面第一個請求 https://accounts.google.com/o/oauth2/v2/auth 是去跟驗證伺服器要一個臨時的通行證,因為有設定 scope 所以這個通行證的使用範圍會受到限制,並且會請問使用者允不允許提供這些資料給我們的網站,得到允許之後 Google 會帶著 AccessToken 發起我們網站設定的路徑請求 也就是上面第二個請求 https://localhost:7011/signin-google 因為 AccessToken 只有要求查看 openid profile email 這三個內容並且 使用者只同意我們網站查看這三份資料的內容,我們最後到資源伺服器也只能拿到三份資料的內容最後 OAuthCreatingTicketContext 會根據資源伺服器給我們的資料 建立出一個 ClaimsPrincipal 也可以理解成 User 並且設定到 HttpContext 內部,我們就可以認定使用者是登入成功了

可以使用之前建立的方法來取得 HttpContext 目前登入 User 的 Claim 來驗證看看 https://localhost:7011/listHttpContextClaim

[
  {
    "claim": {
      "Type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
      "Value": "112873593891211851878"
    }
  },
  {
    "claim": {
      "Type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
      "Value": "Allen Gao"
    }
  },
  {
    "claim": {
      "Type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
      "Value": "Allen"
    }
  },
  {
    "claim": {
      "Type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
      "Value": "Gao"
    }
  },
  {
    "claim": {
      "Type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
      "Value": "allengaodev@gmail.com"
    }
  }
]

Summary

今天學習了如何使用 OAuth2 來取得外部使用者的資料,不過有一個問題是這個外部使用者跟我們當初呼叫 CreateUser 建立的使用者不太一樣 因為他並沒有添加到資料庫內部,所以之後有些權限相關的問題可能會造成困擾例如之前建立的年齡驗證,我們的外部使用者因為沒有在資料庫建立 IdentityUser 所以並沒有辦法添加生日的 Claims,這個問題會在之後解決

今天的進度 Github