# [實作] .Net Framework WebAPI 實作 第三方登入(Google 為例) ### :arrow_forward: **綱要** 1. **第三方登入 是什麼** 2. **OAuth2.0 架構** 3. **本文環境與套件設定** 4. **使用 第三方登入 - 程式碼實作** 5. **(補充) Google API 參考文件** 6. **(補充) 版本相依性錯誤** --- ### 1. 第三方登入 是什麼 是一種帳戶登入作法,使用者透過登入第三方伺服器(如Google),授權予以目標網站(第二方)相關使用者訊息 - **架構**:採用 OAuth2.0 架構,達成間接授權 - **作法**:網站分別取得使用者和第三方的認可,讓使用者可以用第三方帳戶進行目標網站的登入 - **優點**:使用者無須於目標網站建立專屬帳戶,亦即不用記住密碼 - **隱憂**:需要網站本身支援多元的第三方平台 (如 Google、Line 等) --- ### 2. OAuth2.0 架構 ![image 來源](https://upload.wikimedia.org/wikipedia/commons/7/72/Abstract-flow.png) [圖片來源:Wiki 開放授權(OAuth)](https://zh.wikipedia.org/zh-tw/%E5%BC%80%E6%94%BE%E6%8E%88%E6%9D%83) #### (1) 原則:分別取得使用者和第三方的同意以及 Auth Token,就可以透過 Auth Token 取得使用者的特定資源 #### (2) 應用:當網站獲得 Google Token 後,已經間接認證使用者具備 Google 帳戶,如此就可以達到目標網站的登入功能 --- ### 3. 本文環境與套件設定 #### (1) 環境 - IDE:VS-2022 - 框架:.Net Framework 4.7.2 - 專案:ASP NET Web API #### (2) 套件 - Nuget 套件:**Google.Apis.Auth**(1.68.0版) 及 **Google.Apis.PeopleService.v1**(1.68.0.3359版) - Note:初次安裝上述套件,並運行程式,可能出現問題,請參考[下方區域](#(補充)6.-套件版本相依性錯誤) #### (3) Google Console App 服務申請 - 請參考:https://www.tsg.com.tw/blog-detail2-162-0-google-login.htm - 完成上述文章操作後,就會取得「**用戶端ID**」與「**用戶端密碼**」(憑證建立可以參考[下方區域](#(4)-於-Google-Console-中建立憑證與重新導向的路由) - 記得要於 Google Console Project 中啟用 **Google People API** 使用權限(如下圖要啟用,完成後有綠色勾勾) ![image](https://hackmd.io/_uploads/ryP0YrvGC.png) #### (4) 於 Google Console 中建立憑證與重新導向的路由 - 建立 新的憑證 ![image](https://hackmd.io/_uploads/Hyk5YSPG0.png) - 選擇 網頁應用程式 ![image](https://hackmd.io/_uploads/ryVMqSDzC.png) - 定義 相關路由 - Note:"已授權的JavaScript來源",此為前端網站路由,如:`https://sun-live.vercel.app` - Note:"已授權的重新導向 URI",此為重新導向後的路由網址,如:`https://sun-live.vercel.app/auth/verify` - Note:重新導向後的路由網址,路由最後的 "/" 會有差別,需要特別注意 - 如:`https://sun-live.vercel.app/auth/verify` 和 `https://sun-live.vercel.app/auth/verify/`,並不相同 ![image](https://hackmd.io/_uploads/BJ1O9SvGC.png) --- ### 4. 實作 API ![image](https://hackmd.io/_uploads/HJRw1XIGA.png) #### (1) API 1:取得與建立 Google 帳號授權網址 ```csharp public IHttpActionResult API_1() { // 一、建立 ClientSecrets 物件,其中包含 用戶端ID 與 用戶端密碼 物件 var clientSecrets = new ClientSecrets { ClientId = WebConfigurationManager.AppSettings["GoogleId"].ToString(), ClientSecret = WebConfigurationManager.AppSettings["GoogleKey"].ToString() }; // 二、建立 授權資料流 物件,其中夾帶 ClientSecrets 物件 var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = clientSecrets }); // 三、建立 重新導向 路由網址 String(為架構圖中的第7步驟使用), // Note:請再 Google Console 中加入以下網址,否則會失敗(請參考3. 本文環境與套件設定(4)步驟) string redirecturi = @"https://sun-live.vercel.app/auth/verify"; // 請自行修訂 // 四、創立 authorizationUrl 物件,其中夾帶 redirecturi String var authorizationUrl = flow.CreateAuthorizationCodeRequest(redirecturi); // 五、設定 使用者需要允許的授權範圍, // Note:有多個Scope就需要用 空白Space 進行間隔 authorizationUrl.Scope = @"https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email"; // 六、將 authorizationUrl 物件轉變成為 URI物件 // Note:將URI物件中的 Scope 空白Space 替代成為 "%20" // Note:此 authUrlSpace 路由網址,其實是一串 Google 網址,QueryString 夾帶上面所設定的相關參數 Uri authUrl = authorizationUrl.Build(); string authUrlSpace = authUrl.ToString().Replace(" ", "%20"); // 七、將 authUrlSpace 路由 String 送給前端 // Note:請前端讓使用者重新導向到 authUrlSpace 路由(以利於完成架構圖的第7步驟) var result = new { statusCode = 200, status = "success", message = "取得成功", url = authUrlSpace, }; return Content(HttpStatusCode.OK, result); } ``` #### (2) API 2:驗證 Auth Code 並回傳結果 - 前端將會回傳一個物件,內部為一個 String 資料 **code** ,創立一個類別,接收該物件 (如下) - Note:使用者完成指定路由 (authUrlSpace) 的帳號選擇後,將會重新導向到 3. 本文環境與套件設定的[步驟(4)](#(4)-於-Google-Console-中建立憑證與重新導向的路由)路由內(即為 redirecturi String) - Note:Google 會重新導向後,QueryString 會夾帶名為 Code 的 Key 和對應的 Value - Note:請前端 擷取 Code Key 所對應的 Value,並透過 API-2 回傳給予後端 ```csharp public class AuthCode { public string code { get; set; } } ``` ```csharp public async Task<IHttpActionResult> API_2(AuthCode ac) { // 一、建立 ClientSecrets 物件,其中包含 用戶端ID 與 用戶端密碼 物件 var clientSecrets = new ClientSecrets { ClientId = WebConfigurationManager.AppSettings["GoogleId"].ToString(), ClientSecret = WebConfigurationManager.AppSettings["GoogleKey"].ToString() }; // 二、建立 授權資料流 物件,其中夾帶 ClientSecrets 物件 var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = clientSecrets }); // 三、建立 重新導向 路由網址 String // Note:請再 Google Console 中加入以下網址,否則會失敗(請參考3. 本文環境與套件設定(4)步驟) string redirecturi = @"https://sun-live.vercel.app/auth/verify"; // 請自行修訂 // 四、將前端傳入的 ac 物件 進行 URL Decode,取得 Decoding 的 code String string decodedCode = HttpUtility.UrlDecode(ac.code); // 五、使用 ExchangeCodeForToke 方法,取得 Google Auth TokenResponse 物件 // Note:該方法內部的 "user" 值,可為任意值 var tokenResponse = await flow.ExchangeCodeForTokenAsync("user", decodedCode, redirecturi, CancellationToken.None); // 六、使用 UserCredential(),取得 credential 物件 var credential = new UserCredential(flow, "user", tokenResponse); // 七、建立 PeopleService 物件,並夾帶 credential 物件,以利取的使用者的 People API 特定資料 var PeopleService = new PeopleServiceService(new BaseClientService.Initializer() { HttpClientInitializer = credential, ApplicationName = "FarmerAPI" }); // 八、建立與呼叫 Google People API,取得 Google 回傳的 me 物件 // Note:Google API 使用方法可以參考下方 (補充)5.API 文件 GetRequest peopleRequest = PeopleService.People.Get("people/me"); peopleRequest.PersonFields = "names,emailAddresses"; Person me = peopleRequest.Execute(); // 九、將 me 物件打開,儲存到 UserName 和 UserMail 中 string UserName = me.Names[0].DisplayName; string UserMail = me.EmailAddresses[0].Value; // 上述已經取得 Google 所回傳的帳號名稱和 Mail Address,以下就可以開始進行登入或註冊的邏輯 var UserInDB = db.User.Where(x=>x.Acount==UserMail).FirstorDefault(); if(UserInDB!=null) { // 使用者帳號不存在於 DB 內,可進行註冊邏輯 ... return Content(HttpStatusCode.OK, RegisterResult); } else { // 使用者帳號存在於 DB 內,可進行登入邏輯 return Content(HttpStatusCode.OK, LoginResult) } } ``` --- ### (補充)5. Google People API 文件 透過 Google People API,除了可以取得使用者 names 和 emailAddresses 資料,還可以取得其他使用者資訊,可參考以下連結 #### (1) People API 總覽 - 官方文件:https://developers.google.com/people/api/rest/v1/people?hl=zh-tw #### (2) people.get 方法參數 - 官方文件:https://developers.google.com/people/api/rest/v1/people/get?hl=zh-tw --- ### (補充)6. 套件版本相依性錯誤 - 安裝完 Nuget 套件後,運行程式,可能會出現以下錯誤訊息: ![Error](https://hackmd.io/_uploads/SkLl13m7R.png) - 解決方法:切換到上方圖片右下角的"錯誤清單"欄位,並雙點擊內部驚嘆號的錯誤訊息,就會跳出以下視窗: ![image](https://hackmd.io/_uploads/ByJcTiQQ0.png) 點選"是",再重新運行,應該就可以解決套件版本相依性問題了 ---