jim60105 / GoogleOIDC_Angular_ASPNETWebAPI_Auth_Code_Flow

Google OAuth 2.0 OpenID Connect with Authorization Code Flow (Angular + ASP.NET Core Web API) https://blog.maki0419.com/2022/09/angular2-aspnet-webapi-google-oauth2-oidc-auth-code-flow.html

Home Page:https://blog.maki0419.com/2022/09/angular2-aspnet-webapi-google-oauth2-oidc-auth-code-flow.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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/

這台的後端放在 Azure App Service F1 free tier,cold start 時會有點慢,請耐心等待

介紹

使用 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

參考資料

延伸閱讀

About

Google OAuth 2.0 OpenID Connect with Authorization Code Flow (Angular + ASP.NET Core Web API) https://blog.maki0419.com/2022/09/angular2-aspnet-webapi-google-oauth2-oidc-auth-code-flow.html

https://blog.maki0419.com/2022/09/angular2-aspnet-webapi-google-oauth2-oidc-auth-code-flow.html

License:MIT License


Languages

Language:TypeScript 49.8%Language:C# 23.0%Language:HTML 10.5%Language:JavaScript 7.1%Language:CSS 5.6%Language:Dockerfile 4.0%