
User Authentication(Log in、Sign up、Log out)對 Stateful Application 來說是個必要的功能,而目前市面上的 App 幾乎都有提供 Log in with Google 這樣子的 OAuth-based solution,其優點之一就是把 Authentication 的流程簡化了而讓 User 覺得方便。
TL;DR
為了讓 User 能用 Google 帳號登入網站,我們得實作 OAuth-based Google Login,需要一個 Frontend、一個 Backend,並設定好 Resource / Authorization Server (Google OAuth 2.0)。
Steps
- OAuth 2.0 Roles
- OAuth-based Google Login Flow Chart
- Setting up Google OAuth 2.0
- Client Backend
- Client Frontend
1. OAuth 2.0 Roles
從 OAuth 的 role 可以看出 OAuth 的架構並對應到後面的實作,所以在這邊特別列出來。
- Resource owner
在這裡是指要登入網站的 User,Resource 指的是 User 的 email、profile、Google id 等。 - Resource server
就是 Google。 - Client
在這裡是指我們要實作的網站的 Frontend 加 Backend。 - Authorization server
也是 Google,Backend 需要跟他要 OAuth token。
2. OAuth-based Google Login Flow Chart
用一張圖說明接下來要實作什麼。

3. Setting up Google OAuth 2.0
開始實作囉~。
首先在 Google API Console 裡開一個 Project。

接著 Create 一個 OAuth client,取得 Client ID 和 Client Secret,並設定 Authorized Origin 和 Redirect URL。



最後,設定 API 的 Scope,這邊我們只需要 User 的 email、profile 和 openid。

4. Client Backend
使用 PostgresSQL、Express.js 和 google-api-nodejs-client,按照上面的 Flow Chart 來實作。
// .env
GOOGLE_API_CLIENT_ID=
GOOGLE_API_CLIENT_SECRET=
GOOGLE_API_REDIRECT=http://localhost:3000/login-google
// Create Auth URL for Client Frontend
const { google } = require("googleapis");const oAuth2Client = new google.auth.OAuth2(
clientId: process.env.GOOGLE_API_CLIENT_ID,
clientSecret: process.env.GOOGLE_API_CLIENT_SECRET,
redirect: process.env.GOOGLE_API_REDIRECT
);function getAuthUrl() {
return oauth2Client.generateAuthUrl({
access_type: "offline",
prompt: "consent",
scope: [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile"
]
});
}app.get("/login", async (req, res) => {
const url = getAuthUrl()
res.json({ url });
});// Handle Redirect URL
app.get("/login-google", async (req, res) => {
const { code } = req.query;
const { tokens } = await oAuth2Client.getToken(code);
oAuth2Client.credentials = tokens;
const oauth2 = google.oauth2("v2");
// Get Google User
const {
data: { email, id: google_open_id }
} = await oauth2.userinfo.v2.me.get({
auth: oAuth2Client
});
// Upsert User
const { rows } = await client.query(`
insert into
user (email, google_open_id)
values ($1, $2)
on conflict (google_open_id)
do update set email=$1
returning *`,
[email, google_open_id]
);
const user = rows[0];
// Create JWT
var token = jwt.sign(user, secret);
// Login Redirect to Frontend
res.redirect(`${process.env.WEB_BASE_URL}/login/google?token=${token}`);
- access_type: Indicates whether your application can refresh access tokens when the user is not present at the browser.
- prompt: Optional. A space-delimited, case-sensitive list of prompts to present the user.
- scope: OAuth 2.0 Scopes for Google APIs
5. Client Frontend
使用 React,按照上面的 Flow Chart 實作。
首先會跟 Client Backend 拿到 Auth URL,Redirect 過去後就會出現 Google 的 Log in 畫面。
// Get and Open Auth URL
const login = async() => {
const {
data: { url }
} = await axios.request({
method: "get",
url: `${process.env.API_BASE_URL}/login`
});
window.location = url;
}

成功 Log in Google 後 Google 會 Redirect 到 Client Backend,Client Backend 會再帶一個 Token 並 Redirect 回 Client Frontend,最後 Frontend 就得處理這個 Login Request 和 Token。
// Handle Login Request
import { useEffect } from "react";
import qs from "qs";useEffect(() => {
const token =
qs.parse(
props.location.search,
{ ignoreQueryPrefix: true }
).token || localStorage.getItem("token"); if (token) {
// Save the JWT (JSON Web Token)
localStorage.setItem("token", token);
...
} else {
// Unautherized
}
}, []);
Demo through Paaaack!

