哈囉大家好!
昨天處理完User Table和Neon資料庫後,要把前端收到的Token和使用者資訊(例如:姓名、郵件...等)儲存到User Table裡面。
需要完成的部分有以下步驟:
response.credential
取得ID Token(JWT)。HttpClient
將這個ID Token發送到後端API。HttpOnly
Cookie或Local Storage中,並用於後續的API呼叫。登入後,callback function handleCredentialResponse() 會接收到Google傳送的response,
將response.credential用之前定義好的decodeJWT()decode後就可以拿到JWT:
handleCredentialResponse(response: any): void {
const responsePayload = this.decodeJWT(response.credential);
}
先定義一個ApiService:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from '../environments/environment.development';
import { TokenRequest } from '../interfaces/auth';
@Injectable({
providedIn: 'root',
})
export class ApiServiceService {
constructor(private http: HttpClient) {}
saveIdToken(tokenRequest: TokenRequest) {
const url = `${environment.apiBaseUrl}/auth/save-token`;
return this.http.post(url, { tokenRequest });
}
}
之後在LoginComponent的constructor注入這個服務,並呼叫儲存Id Token的function:
handleCredentialResponse(response: any): void {
// 1. JWT處理
const responsePayload = this.decodeJWT(response.credential);
// 2. 將id token 存到local Storage或儲存到後端
const tokenRequest: TokenRequest = {
googleSubId: responsePayload.sub,
email: responsePayload.email,
name: responsePayload.name,
};
this.apiService.saveIdToken(tokenRequest).subscribe({
next: (res) => {
console.log('ID token saved successfully:', res);
// 3. 登入狀態更新
this.loggedIn = true;
this.router.navigate(['/records']);
},
error: (err) => {
console.error('Error saving ID token:', err);
this.loggedIn = false;
},
});
}
接著需要用Google官方的library來安全地驗證JWT ID Token,
dotnet add package Google.Apis.Auth
using System.ComponentModel.DataAnnotations;
namespace GoDutchBackend.Models
{
public class TokenRequest
{
[Required]
public string GoogleSubId { get; set; } = string.Empty;
[Required]
public string Email { get; set; } = string.Empty;
[Required]
public string Name { get; set; } = string.Empty;
}
}
接著再定義Interface IAuthService:
namespace GoDutchBackend.Services
{
public interface IAuthService
{
Task<string?> AuthenticateGoogleUserAsync(string googleIdToken);
}
}
using GoDutch.Data;
using GoDutch.Models;
using Google.Apis.Auth;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace GoDutchBackend.Services
{
public class AuthService : IAuthService
{
private readonly string _googleClientId;
private readonly AppDbContext _dbContext;
private readonly ILogger<AuthService> _logger;
public AuthService(IConfiguration configuration, AppDbContext dbContext, ILogger<AuthService> logger)
{
// 從appsettings.json讀取 client ID
_googleClientId = configuration["GoogleAuth:ClientId"] ?? throw new ArgumentNullException("Google Client ID is not configured.");
_dbContext = dbContext;
_logger = logger;
}
public async Task<string?> AuthenticateGoogleUserAsync(string googleIdToken)
{
try
{
// 1. 驗證google Id Token
var payload = await GoogleJsonWebSignature.ValidateAsync(googleIdToken, new GoogleJsonWebSignature.ValidationSettings
{
Audience = new[] { _googleClientId }
});
// 2. 處理使用者資料
var subId = payload.Subject; // google User唯一不變id(GoogleSubId)
var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.GoogleSubId == subId);
if (user == null) // 第一次登入
{
user = new User
{
GoogleSubId = subId,
Email = payload.Email,
Name = payload.Name,
CreatedAt = DateTime.Now
};
_dbContext.Users.Add(user);
await _dbContext.SaveChangesAsync();
_logger.LogInformation("New users created: {Email}", user.Email);
}
// 3. 產生專案專屬的session Token
// 產生session token的部分會保留到明天!
return $"temporary_fake_session_token";
}
catch (InvalidJwtException ex)
{
_logger.LogError(ex, "Invalid GoogleId Token.");
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "AuthService.AuthenticateGoogleUserAsync failed.");
return null;
}
}
}