iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
佛心分享-SideProject30

30天的旅程!從學習C#到開發小專案系列 第 19

DAY 19 - Google SignIn Button 登入功能

  • 分享至 

  • xImage
  •  

哈囉大家好!
今天要來繼續處理Google SignIn Button的登入功能~
其實突然發現我現在完全沒在用之前介紹的那個套件XD 直接用官方的GSI按鈕了!
背後原因是不知道為什麼已經安裝了套件,但是其中有個按鈕模組一直顯示不存在,
想說與其花時間解決模組的問題直接另闢它徑好像也不錯XD
而且官方的按鈕很漂亮!
那就直接進入今天的主題吧~

目標:使用者透過GSI按鈕登入後,跳轉至http://localhost:4200/records 頁面。

回顧昨天的html按鈕,修正有誤的部分

<div class="flex justify-center items-center p-6">
  <div
    id="g_id_onload"
    data-client_id="我的Client ID"
    data-context="signin"
    data-ux_mode="popup"
    data-login_uri="http://localhost:4200"
    data-auto_prompt="false"
    data-callback="handleCredentialResponse"
  ></div>

  <div
    class="g_id_signin"
    data-type="standard"
    data-shape="pill"
    data-theme="outline"
    data-text="signin_with"
    data-size="large"
    data-logo_alignment="left"
  ></div>
</div>

首先發現的第一個問題就是這個按鈕的設置存在衝突
必須先解釋Google Identity Services的兩個概念:GSI有提供兩種登入模式:

  1. 重導向模式 (redirect)
  2. 前端回調 (callback)

但我原本的按鈕設定中,我設定了data-callback="handleCredentialResponse",表示我希望GSI在前端執行JavaScript的function來處理回傳的Token。但這個設定和html中出現的data-ux_mode="redirect"還有data-login_uri

  • data-ux_mode="redirect"data-login_uri:這兩個屬性是用在Google處理完畢後,將使用者重新導向一個新的頁面。
  • data-callback:用在Google處理完後,在當前的頁面執行JavaScript function。
    簡單來說,重導向模式和回調模式同時存在了,會導致報錯。

因為popup模式是產生一個新的登入彈跳視窗,不會導致頁面重新載入,可以更順暢的在前端處理Token,所以我決定要使用前端回調模式~~

來看看修正後的程式碼:

<div
  id="g_id_onload"
  data-client_id="我的Client ID"
  data-context="signin"
  data-ux_mode="popup"  
  data-auto_prompt="false"
  data-callback="handleCredentialResponse" 
></div>

<div
  class="g_id_signin"
  data-type="standard"
  data-shape="rectangular"
  data-theme="outline"
  data-text="signin_with"
  data-size="large"
  data-logo_alignment="left"
></div>

處理接收JWT邏輯

解決完按鈕設定的問題後,就可以來處理接收Token和重新導向的部分!
目前按鈕的設定會讓前端拿到Token(handleCredentialResponse會被呼叫並接收Google傳的Token)
這裡要先寫好一個decode Token的function,等一下會在callback function裡面用到:

decodeJWT(token: string) {
    let base64Url = token.split('.')[1];
    let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    let jsonPayload = decodeURIComponent(
      atob(base64)
        .split('')
        .map(function (c) {
          return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        })
        .join('')
    );
    return JSON.parse(jsonPayload);
  }

接下來就可以寫callback function!接收Google傳來的Token:

handleCredentialResponse(response: any): void {
    console.log(`Encoded JWT ID token: ${response.credential}`);

    // decode JWT
    const responsePayload = this.decodeJWT(response.credential);

    // 登入狀態更新
    this.loggedIn = true;
    this.router.navigate(['/records']); // 將使用者導向新頁面

    // 將id token 存到local Storage或儲存到後端
    localStorage.setItem('id_token', response.credential);
  }

特別的是,必須確保這個callback function是個全域function,所以在lifecycle ngOnInit() 要做一下處理:

ngOnInit(): void {
    if (typeof window !== 'undefined') {
      (window as any).handleCredentialResponse =
        this.handleCredentialResponse.bind(this);
    }
}

完整的程式碼如下:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrl: './login.component.css',
  standalone: true,
})
export class LoginComponent implements OnInit {
  loggedIn: boolean = false;

  constructor(private router: Router) {}

  ngOnInit(): void {
    if (typeof window !== 'undefined') {
      (window as any).handleCredentialResponse =
        this.handleCredentialResponse.bind(this);
    }
  }
  
  decodeJWT(token: string) {
    let base64Url = token.split('.')[1];
    let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    let jsonPayload = decodeURIComponent(
      atob(base64)
        .split('')
        .map(function (c) {
          return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        })
        .join('')
    );
    return JSON.parse(jsonPayload);
  }
}

  handleCredentialResponse(response: any): void {
    console.log(`Encoded JWT ID token: ${response.credential}`);

    const responsePayload = this.decodeJWT(response.credential);

    this.loggedIn = true;
    this.router.navigate(['/records']);

    localStorage.setItem('id_token', response.credential);
  }

經過測試後可以順利登入並跳轉畫面了!但應該要把Token發送給後端存在資料庫裡面,所以打算明天來完成剩下的部分/images/emoticon/emoticon08.gif


上一篇
DAY 18 - 設定Google Sign-In 按鈕
系列文
30天的旅程!從學習C#到開發小專案19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言