Skip to content
This repository was archived by the owner on Sep 26, 2023. It is now read-only.

jim60105/GoogleOIDC_Angular_ASPNETWebAPI_Auth_Code_Flow

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Google OAuth 2.0 OpenID Connect with Authorization Code Flow
(Angular + ASP.NET Web API)

google-oauth-angular-netcorewebapi

Demo site

https://googleoidcdemo.maki0419.com/

介紹

使用 Angular 實現 OpenID Connect 登入,網路上的教學大都是使用隱含流程(Implicit flow)。
隱含流程不需要後端,適合 Angular 這種純前端應用。
它的設定比較簡單,設備維護成本低,但相較於授權碼流程(Authorization code flow)來說 隱含流程的安全性較低

在較為大型的 Angular 應用程式專案中,常會搭配後端API以處理資料庫或是複雜的應用邏輯。
或許你不會為了 OIDC 而多開一台後端伺服器,但倘若只是把授權碼流程的一部份搬到現有的後端API,這就成為了一個划算的決定。

本專案使用 Angular + ASP.NET Web API,示範如何在前後端分離的專案中實作 Google OAuth2 OIDC 登入。

  • C# ASP.NET Core 6 Web API 專案
    • OIDC僅使用 Google 官方的 Google.Apis.Auth 套件
  • Angular 14 前端專案
    • OIDC僅使用 Google 官方的 Google Sign-In 客戶端套件

本文重點主要是在於實作,而非OAuth 2.0的流程講解
如果想要深入學習,請參考保哥的課程 《精通 OAuth 2.0 授權框架》

Try it out

  • Google API Console 註冊新的專案,「憑證→OAuth 2.0 用戶端 ID→網頁應用程式」取得「用戶端編號」、「用戶端密碼」

  • 在 「已授權的重新導向 URI」填入 https://localhost:7091/api/Auth/oidc/signin

  • Git clone

    git clone https://github.com/jim60105/GoogleOIDC_Angular_ASPNETWebAPI_Auth_Code_Flow.git
  • ASP.NET Web API

    • Visual Studio 啟動 ASPNET_WebAPI/GoogleOIDC_Angular_ASPNETWebAPI_Auth_Code_Flow.sln
    • 修改 ASPNET_WebAPI\appsettings.json 檔案
      • YOUR CLIENT ID 填入「用戶端編號」
      • YOUR CLIENT SECRET 填入「用戶端密碼」
      • RedirectUri 若留空字串則會彈性使用 runtime http request 進來時使用的 host;或者你也可以在這裡填入設定
    • 以 Debug 模式啟動但不偵錯 (Ctrl+F5),Swagger 將啟動在 https://localhost:7091
  • Angular

    • Visual Studio Code 啟動 Angular 目錄

    • 修改 Angular\src\environments\environment.ts 檔案

      • YOUR CLIENT ID 填入「用戶端編號」
    • npm install 並啟動伺服器

      npm install
      npm run-script start
  • 訪問 https://localhost:4200/

套件安裝

授權流程

  1. 用戶在前端按下 Sign in with Google 按鈕
  2. 以 gsi 客戶端套件啟動授權碼流程
    oidcLogin(): void {
    const client: google.accounts.oauth2.CodeClient = google.accounts.oauth2.initCodeClient({
    client_id: environment.clientId,
    scope: 'openid profile email',
    ux_mode: 'redirect',
    redirect_uri: environment.apiUrl + '/api/Auth/oidc/signin',
    state: '12345GG',
    });
    client.requestCode();
    }
  3. 導向至 Google OAuth 授權頁面
  4. (使用者同意後),導向至 後端/api/Auth/oidc/signin,Model Binding 取得授權碼
    若是使用者拒絕,或是發生了任何失敗,error 參數就會接到內容
    public async Task<IActionResult> SigninOIDCAsync(string code, string state, string? error)
    {
    if (state != "12345GG")
    {
    return BadRequest();
    }
    if (!string.IsNullOrEmpty(error))
    {
    throw new Exception(error);
    }
    string idToken = await _oidcService.GetIdTokenAsync(code);
    return Redirect($"{_config["FrontEndUri"]}?idToken={idToken}");
    }
  5. 以授權碼去要回 idToken
    public async Task<string> GetIdTokenAsync(string authorization_code)
    {
    using HttpClient client = _httpClientFactory.CreateClient();
    AuthorizationCodeTokenRequest request = new()
    {
    Code = authorization_code,
    RedirectUri = RedirectUri,
    ClientId = ClientId,
    ClientSecret = ClientSecret,
    Scope = "openid profile email"
    };
    TokenResponse responce = await request.ExecuteAsync(client,
    GoogleAuthConsts.OidcTokenUrl,
    new(),
    Google.Apis.Util.SystemClock.Default);
    return responce.IdToken;
    }
  6. 導向回前端,將 idToken 以網址參數傳給 Angular
    return Redirect($"{_config["FrontEndUri"]}?idToken={idToken}");
  7. Angular 前端應用程式接到 idToken
    this.route.queryParamMap.subscribe((params: ParamMap) => {
    this.idToken = params.get('idToken') || '';
    if (this.idToken) {
    // 直接解開JWT Token
    this.authenticationService.extractToken(this.idToken).subscribe((res) => {
    this.user = res;
    });
    // 如果需要在前端驗證Token,可以使用這個Google API
    this.authenticationService.verifyToken(this.idToken).subscribe({
    next: (data) => {
    console.log('This is verified by Google', data);
    },
    error: (error) => {
    console.log(error);
    },
    });
    // TODO: 接下來可以將idToken存到localStorage,並且在每次發送API請求時,將idToken放到Authorization Header。後端只要用一樣的方式驗證idToken即可確認身份。
    }
    });
  8. 將 idToken 做 JWT decode,取得內容物
    const { sub: userID, name: userName, id: id, picture, email } = jwtDecode<any>(idToken);

如果需要在前/後端驗證 JWT Token 的有效性,可以叫這個 api
Google 會驗證簽章、簽發者、有效期,並在驗證通過時返回內容
(直接 JWT decode 會比打這個 API 要來得快,建議只在需要由 Google 驗證時呼叫它)
https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123
參考來源:
https://developers.google.com/identity/sign-in/web/backend-auth#calling-the-tokeninfo-endpoint

參考資料

延伸閱讀