哈囉大家好!
今天要來繼續處理Google SignIn Button的登入功能~
其實突然發現我現在完全沒在用之前介紹的那個套件XD 直接用官方的GSI按鈕了!
背後原因是不知道為什麼已經安裝了套件,但是其中有個按鈕模組一直顯示不存在,
想說與其花時間解決模組的問題直接另闢它徑好像也不錯XD
而且官方的按鈕很漂亮!
那就直接進入今天的主題吧~
目標:使用者透過GSI按鈕登入後,跳轉至http://localhost:4200/records 頁面。
<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有提供兩種登入模式:
但我原本的按鈕設定中,我設定了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>
解決完按鈕設定的問題後,就可以來處理接收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發送給後端存在資料庫裡面,所以打算明天來完成剩下的部分