The Own Lab The Own Lab

Google Login 實作

Google Cloud Console 設定、OAuth 授權流程與 ID Token 驗證實務

Overview##

上一篇介紹了 OAuth 2.0 和 OIDC 的原理。這篇聚焦在 Google Login 的實際設定和實作——從 Google Cloud Console 設定到完整的登入流程。

Google Login 基於標準的 OAuth 2.0 Authorization Code Flow + OIDC,是最常見的社群登入方式之一。

sequenceDiagram
  participant U as User
  participant C as Your App
  participant G as Google

  U->>C: Click Login with Google
  C->>G: Redirect to /authorize
  G->>U: Consent screen
  U->>G: Grant permission
  G->>C: Redirect with code
  C->>G: Exchange code for tokens
  G->>C: access_token + id_token
  C->>C: Verify ID Token
  C->>U: Login success

Note

Google Login 同時支援 Web、Android、iOS。本文以 Web(server-side)為主。

Setup##

Google Cloud Console 設定###

  1. 前往 Google Cloud Console
  2. 建立或選擇 Project
  3. APIs & Services → Credentials → Create Credentials → OAuth Client ID
  4. 設定 OAuth consent screen(應用名稱、logo、scope)
  5. Application type 選 Web application
  6. 設定 Authorized redirect URIs
# 開發環境
http://localhost:3000/auth/google/callback

# 生產環境
https://yourapp.com/auth/google/callback

取得 Client IDClient Secret

設定說明
User TypeExternal(公開)或 Internal(僅組織內)
App Name顯示在授權頁面的應用名稱
Scopes請求的權限(建議最小化)
Test Users開發階段的測試帳號

Warning

新建立的 OAuth App 預設在「Testing」狀態,只有 Test Users 能登入。要開放給所有使用者,需要通過 Google 的驗證審核(Verification),審核通常需要數天到數週。

Usage##

Step 1 — 導向授權頁面###

// GET /auth/google
import crypto from 'crypto';

function getGoogleAuthUrl(): string {
  const state = crypto.randomBytes(16).toString('hex');
  const nonce = crypto.randomBytes(16).toString('hex');
  // Store state and nonce in session for later verification

  const params = new URLSearchParams({
    client_id: process.env.GOOGLE_CLIENT_ID!,
    redirect_uri: 'https://yourapp.com/auth/google/callback',
    response_type: 'code',
    scope: 'openid email profile',
    state,
    nonce,
    access_type: 'offline', // Request refresh token
    prompt: 'consent', // Force consent screen
  });

  return `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
}
參數說明
scopeopenid email profile 取得身份、email、名稱和頭像
access_typeoffline 才會回傳 refresh token
promptconsent 強制顯示同意畫面(確保拿到 refresh token)

Step 2 — 處理回調###

// GET /auth/google/callback
async function handleGoogleCallback(code: string, state: string) {
  // 1. Verify state matches session
  // 2. Exchange code for tokens
  const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      code,
      client_id: process.env.GOOGLE_CLIENT_ID!,
      client_secret: process.env.GOOGLE_CLIENT_SECRET!,
      redirect_uri: 'https://yourapp.com/auth/google/callback',
      grant_type: 'authorization_code',
    }),
  });

  const tokens = await tokenRes.json();

  // 3. Verify and decode ID token
  const { OAuth2Client } = await import('google-auth-library');
  const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
  const ticket = await client.verifyIdToken({
    idToken: tokens.id_token,
    audience: process.env.GOOGLE_CLIENT_ID,
  });

  const payload = ticket.getPayload()!;

  // 4. Find or create user in database
  const user = await findOrCreateUser({
    provider: 'google',
    providerId: payload.sub, // Unique, stable ID
    email: payload.email!,
    name: payload.name!,
    picture: payload.picture,
  });

  return user;
}

Step 3 — 使用 Access Token 取得更多資料###

如果需要 ID Token 以外的資料(如 Google Calendar、Drive),用 access token 呼叫 Google API:

// Use access token to call Google APIs
const userInfoRes = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
  headers: { Authorization: `Bearer ${tokens.access_token}` },
});

const userInfo = await userInfoRes.json();
// { sub, name, given_name, family_name, picture, email, email_verified, locale }

Configuration##

Google OAuth Scopes###

Scope提供的資料
openid使用者的唯一 ID(sub
emailEmail 地址 + 是否已驗證
profile名稱、頭像 URL、locale

Tip

只請求你真正需要的 scope。請求過多權限會讓使用者在 consent screen 上猶豫,降低轉換率。大多數登入場景只需要 openid email profile

ID Token Payload###

Google 回傳的 ID Token(JWT)包含:

欄位說明範例
sub唯一 ID(不變)"1234567890"
emailEmail"john@gmail.com"
email_verifiedEmail 是否已驗證true
name全名"John Doe"
picture頭像 URL"https://lh3.google..."
given_name"John"
family_name"Doe"
locale語言"zh-TW"

資料庫 Schema###

支援多 provider 的設計:

CREATE TABLE users (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email       VARCHAR(255),
  name        VARCHAR(255),
  avatar_url  TEXT,
  created_at  TIMESTAMP DEFAULT NOW()
);

CREATE TABLE oauth_accounts (
  id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id      UUID REFERENCES users(id),
  provider     VARCHAR(50) NOT NULL,  -- 'google' | 'apple'
  provider_id  VARCHAR(255) NOT NULL, -- sub from ID token
  email        VARCHAR(255),
  UNIQUE(provider, provider_id)
);

Tip

oauth_accounts 表而非在 users 表加 google_id 欄位。這樣可以輕鬆擴展到更多 provider,也支援一個使用者綁定多個登入方式。

Quiz##

Single Choice

Google OAuth 的 access_type: 'offline' 參數的作用是什麼?

Single Choice

為什麼要用 ID Token 的 sub 而非 email 識別使用者?

Single Choice

新建立的 Google OAuth App 預設在什麼狀態?

Summary##

  • Google Login 基於標準 OAuth 2.0 Authorization Code Flow + OIDC
  • 在 Google Cloud Console 建立 OAuth Client ID,取得 client_idclient_secret
  • google-auth-library 驗證 ID Token,以 sub 識別使用者
  • access_type: 'offline' 取得 refresh token,prompt: 'consent' 確保顯示同意畫面
  • 新 App 預設在 Testing 狀態,開放使用需通過 Google 驗證審核
  • 資料庫用 oauth_accounts 表支援多 provider 擴展

留言 (0)

登入後即可留言