OAuth 2.0 第三方登入實作教學:用 Node.js 整合 Google 與 GitHub 登入
「不是吧,2026 年了還有人在自己做密碼登入?」——好吧,其實很多專案確實還是需要帳號密碼登入,但 OAuth 2.0 第三方登入幾乎已經是現代 Web 應用的標配了。Google、GitHub、Facebook、Apple,使用者用已有的帳號一鍵登入,不用再記一組新密碼,你也不用操心密碼儲存的安全問題。
我在實際專案中用過不少次 OAuth,踩過的坑也不少。這篇教學會從 OAuth 2.0 的核心概念開始,帶你用 Node.js + Express 完整實作 Google 和 GitHub 的第三方登入。如果你之前用過 JWT 認證,那 OAuth 2.0 對你來說應該不會太陌生。
OAuth 2.0 是什麼?為什麼不自己做登入
OAuth 2.0 是一個授權框架(Authorization Framework),核心思想是:讓使用者在不洩露密碼的前提下,授權第三方應用存取他在另一個服務上的資源。
為什麼要用它而不是自己做登入?三個關鍵理由:
- 安全性:你不需要儲存使用者的密碼,密碼驗證完全由 Google/GitHub 處理
- 使用者體驗:一鍵登入比填寫註冊表單的轉換率高出 2-3 倍
- 信任度:使用者更願意信任知名平台的認證機制
OAuth 2.0 定義了四個角色:Resource Owner(使用者)、Client(你的應用)、Authorization Server(Google/GitHub 的授權伺服器)、Resource Server(存放使用者資料的伺服器)。
Authorization Code Flow 完整解析
OAuth 2.0 有好幾種授權模式,但對於 Server-side Web 應用來說,Authorization Code Flow(授權碼模式)是最安全的選擇。流程如下:
- 使用者點擊「用 Google 登入」按鈕
- 你的後端把使用者重新導向到 Google 的授權頁面
- 使用者在 Google 頁面上確認授權
- Google 把使用者重新導向回你的 Callback URL,並附上一個 Authorization Code
- 你的後端用這個 Code 向 Google 的 Token Endpoint 換取 Access Token
- 拿到 Access Token 後,就可以向 Google API 請求使用者資料
為什麼不直接拿 Access Token,要多這個 Code 的步驟?因為 Code 是透過瀏覽器的 URL 參數傳遞的,如果直接傳 Token,可能會被瀏覽器歷史紀錄或 Referrer Header 洩漏。Code 只能使用一次,而且必須搭配 Client Secret 才能換取 Token,安全性高很多。
專案環境設定
先建立一個新的 Node.js 專案:
mkdir oauth-demo && cd oauth-demo
npm init -y
npm install express passport passport-google-oauth20 passport-github2 express-session dotenv
我們用 Passport.js 來處理 OAuth 流程。Passport 是 Node.js 生態系最主流的認證中介軟體,它用「策略(Strategy)」模式來支援不同的認證方式,包括 Google、GitHub、Facebook 等幾乎所有主流的 OAuth Provider。
專案結構大致如下:
oauth-demo/
├── .env
├── app.js
├── config/
│ └── passport.js
├── routes/
│ └── auth.js
└── package.json
Google OAuth 設定與實作
首先到 Google Cloud Console 建立一個新專案,然後啟用 Google+ API(或 People API)。在「憑證」頁面建立 OAuth 2.0 Client ID,設定授權的重新導向 URI 為 http://localhost:3000/auth/google/callback。
拿到 Client ID 和 Client Secret 後,寫入 .env 檔案。接著設定 Passport 的 Google Strategy:
// config/passport.js
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback'
}, async (accessToken, refreshToken, profile, done) => {
// 用 profile.id 查找或建立用戶
const user = await findOrCreateUser({
provider: 'google',
providerId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
avatar: profile.photos[0].value
});
return done(null, user);
}));
路由設定也很簡單:
// routes/auth.js
router.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] }));
router.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => res.redirect('/dashboard'));
GitHub OAuth 設定與實作
GitHub 的設定更簡單。到 GitHub Settings > Developer settings > OAuth Apps,建立新的 OAuth App,設定 Callback URL 一樣是 http://localhost:3000/auth/github/callback。
GitHub Strategy 的設定跟 Google 幾乎一模一樣。但有個小差異:GitHub 的使用者 email 可能是 private 的,你需要額外用 Access Token 呼叫 /user/emails API 才能拿到。這是很多新手會漏掉的地方。
如果你的專案同時有版本化 API,建議把 OAuth 相關的路由放在獨立的 Router 裡,不要跟 API 路由混在一起。
Access Token 與 Refresh Token 管理
Access Token 通常有效期很短(Google 預設 1 小時),過期後你需要用 Refresh Token 來換取新的 Access Token,而不是要求使用者重新授權。
關鍵原則:
- Access Token 可以存在 Session 或快取中
- Refresh Token 必須安全地存在資料庫
- 絕對不要把任何 Token 存在前端的 localStorage(容易被 XSS 攻擊竊取)
如果你的應用有搭配 Redis 快取,可以把 Access Token 存在 Redis 中,設定跟 Token 有效期一樣的 TTL,這樣就能自動過期清除。
用戶 Session 與資料庫整合
使用者透過 OAuth 登入後,你需要在自己的資料庫建立一筆用戶記錄。通常的做法是建立一個 users 表和一個 oauth_accounts 表:
-- users 表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255),
avatar_url TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- oauth_accounts 表(一個用戶可能有多個 OAuth 帳號)
CREATE TABLE oauth_accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
provider VARCHAR(50) NOT NULL,
provider_id VARCHAR(255) NOT NULL,
access_token TEXT,
refresh_token TEXT,
UNIQUE(provider, provider_id)
);
這樣設計的好處是,同一個使用者可以同時綁定 Google 和 GitHub 帳號。如果使用者先用 Google 登入,之後又用 GitHub 登入,只要 email 一樣,就可以自動關聯到同一個帳號。
OAuth 2.0 安全性檢查清單
OAuth 2.0 的安全性不只是「用了就安全」。以下是你必須檢查的項目:
- CSRF 保護:在授權請求中加入 state 參數,Callback 時驗證 state 是否一致
- PKCE(Proof Key for Code Exchange):額外的安全層,建議所有情境都啟用
- Redirect URI 白名單:只允許預先設定的 Callback URL,防止 Open Redirect 攻擊
- Token 儲存安全:Refresh Token 必須加密儲存,Access Token 不要暴露給前端
- Scope 最小權限:只請求你需要的權限範圍,不要貪心
特別提一下,如果你的 API 還有 Rate Limiting 機制,記得 OAuth Callback 的路由也要保護好,避免被惡意大量觸發。
常見錯誤與除錯技巧
以下是我在實戰中遇過最多的問題:
- redirect_uri_mismatch:Callback URL 在 Google Console 和程式碼中必須完全一致,包括結尾的斜線
- Token 過期沒處理:Access Token 過期後直接回 401,前端不知道怎麼處理。正確做法是後端自動用 Refresh Token 換取新 Token
- Session 序列化問題:Passport 的 serializeUser 和 deserializeUser 一定要正確實作,否則登入後每次請求都找不到用戶
- 開發環境用 HTTP 但生產要 HTTPS:Google 強制要求 HTTPS(localhost 除外),部署時忘記這個會踩到坑
結語與延伸學習
OAuth 2.0 第三方登入的核心概念其實不難,但魔鬼在細節裡。Authorization Code Flow 的每個步驟都有其安全考量,跳過任何一個都可能留下漏洞。
如果你的專案正在做更完整的後端架構,建議接著看微服務架構入門,了解在分散式系統中如何統一管理認證。或者看看 GraphQL vs REST API 的比較,思考你的 API 架構該怎麼設計。
有問題歡迎留言討論,我會持續更新這篇教學。下篇見!
繼續閱讀
API 限流器完整指南:令牌桶與滑動窗口演算法 Node.js 實作教學
相關文章
你可能也喜歡
探索其他領域的精選好文