使用 Express + Passport 實作一個 Google OAuth 2.0 登入

前言

這一篇稍微紀錄分享一下如何使用 Express + Passport.js 來實作一個驗證 or 會員機制,但這一篇必須先注意到一件事情,並不會完整講到資料庫的部分,只會介紹到接上 Google 登入。

申請 Google OAuth 2.0

一開始請先進入到 Google Cloud Platform 頁面建立一個新專案,建立新專案的過程就不額外說明了,因此直接進入正題。

假使你已經建立好專案,那請點一下畫面的「API 與服務

API 與服務

或是側邊欄也可以找到「API 與服務

API 與服務

第一步驟:OAuth 同意畫面

在「API 與服務」的側邊欄中有一個「OAuth 同意畫面」選項點它,接下來左邊選擇「外部」並「提交

OAuth 同意畫面

進入到「編輯應用程式註冊申請」畫面後,只需要填寫紅色井字號欄位,分別是

  • 應用程式名稱
  • 使用者支援電子郵件
  • 應用程式首頁
    • 先填寫 Express 預設 Port → 「http://localhost:3000
  • 開發人員聯絡資訊 => 電子郵件地址

填寫完畢後就按下「儲存並繼續

編輯應用程式註冊申請

第二頁面不用理它,直接在按下「儲存並繼續

範圍

第三個頁面是測試使用者,也不用理他,繼續按下「儲存並繼續

可測試的使用者

最後一頁面就是「摘要」,這個頁面你可以稍微看一下,而這邊也代表著「OAuth 同意畫面」已經設定完畢了。

第二步驟:建立憑證

接下來要建立憑證,一樣點一下旁邊「憑證」並且點一下右邊上方「建立憑證」,選「OAuth 用戶端 ID

建立憑證

接下來要選擇應用程式類型跟相關設定

  • 應用程式類型
    • 選「網頁應用程式」,因為我們會使用 Express 實作。
  • 名稱
    • 隨便你,我是打「passport-test
  • 已授權的重新導向 Url

以上沒問題後就按下「建立

建立 OAuth 用戶端 ID

建立成功後你就可以取得用戶端 ID 跟用戶端密碼了

OAuth 用戶端已建立

這兩個請保存好,稍後 Express 會使用到。

Passport

開始之前我們簡單認識一下 Passport 是什麼,Passport 是一款專門處理驗證的一款 middleware 套件,而它提供了相當多的驗證策略高達 500 多種,這絕對不是我在亂講跟誇飾,是官方網站自己就提到這件事情。

這次範例主要是 Google 登入並預設立場為前後端分離,那麼就準備開始吧!

Express 專案建立

接下來的專案我將會使用 Express 應用程式產生器 產生一個 Express 專案,基本上初始指令如下,以下是終端機指令

1
2
3
mkdir passport-example // 建立一個 passport-example 資料夾
cd passport-example // 移動到 passport-example 資料夾
express --view=ejs // 產生一個 Express 專案

接下來就是準備開始安裝相關套件,那我們這專案會使用到 passportpassport-google-oauth20 這兩個套件

1
npm install --save passport passport-google-oauth20

安裝完畢以上套件後,建議輸入一下 npm start 試跑一下確定並沒有任何問題,若沒有問題的話,我們就準備進入開發步驟囉~

路由撰寫

首先先建立一個 router/auth.js 的路由檔案,內容先如下:

1
2
3
4
5
6
7
8
9
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});

module.exports = router;

接下來我們會需要建立兩個路由,分別是 /google/google/callback

先來講第一個路由怎麼寫,第一個路由基本上我們會單純的使用 passport,而會使用的是它的 authenticate 方法,它可以傳入兩個參數,分別是驗證類型跟資料範圍,而這邊我們示範上需要,所以就會需要使用者的 profile 跟 email,因此寫法如下

1
2
3
router.get('/google', passport.authenticate('google', {
scope: [ 'email', 'profile' ],
}));

(scope 的部分你可以 這個 連結底下看到你可以取得哪些使用者資料。)

另一個 /google/callback,當使用者透過 /google 路由之後會被轉址到 Google 登入頁面

登入頁面

登入成功後,Google 會依據你稍後我們後面會介紹的 passport.use 來決定你要導向到哪一個路由,因此 /google/callback 就要如下方這樣撰寫,但這時候要注意我因為是預設立場於前後端分離狀況下,因此並不會需要紀錄 session,所以會多傳入一個 session 的設置,執行完畢後才會往裡面的 callback 運作

1
2
3
4
5
6
7
8
9
router.get('/google/callback', passport.authenticate('google', { session: false }), (req, res) => {
res.send({
status: true,
data: {
id: req.user.id,
name: req.user.displayName
}
});
})

那麼路由這邊是最簡單的,接下來我們要往 passport 去做設置囉~

別忘了到 app.js 加入這個 router

1
2
3
4
5
6
const authRouter = require('./routes/auth');

app.use('/auth', authRouter);
...

module.exports = app;

驗證策略設定

前面路由雖然寫好了,但是實際上我們還沒設置好 passport,這邊因為我們會使用到 Google 登入,所以在 passport-google-oauth20 官方文件中就有提供現成的範例程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const passport = require('passport');
const GoogleStrategy = require( 'passport-google-oauth20' ).Strategy;

passport.use(new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: "http://www.example.com/auth/google/callback"
},
function(accessToken, refreshToken, profile, cb) {
User.findOrCreate({ googleId: profile.id }, function (err, user) {
return cb(err, user);
});
}
));

這以上這一段可以先貼到 app.js 內,然後將 clientIDclientSecretcallbackURL 改成以下

  • clientID
  • clientSecret

這兩個基本上就是剛剛前面的用=戶端 ID 跟用戶端密碼

OAuth 用戶端已建立

  • callbackURL
    • 填寫成 http://localhost:3000/auth/google/callback,主要對應我們前面的 /google/callback 路由。

接下來下方有一個 callback function

1
2
3
4
5
function(accessToken, refreshToken, profile, cb) {
User.findOrCreate({ googleId: profile.id }, function (err, user) {
return cb(err, user);
});
}

裡面的語法其實是 MongoDB 的語法,但這邊我們暫時用不到,全部砍掉成以下

1
2
3
4
5
6
7
8
9
10
11
12
const passport = require('passport');
const GoogleStrategy = require( 'passport-google-oauth20' ).Strategy;

passport.use(new GoogleStrategy({
clientID: "GOOGLE_CLIENT_ID",
clientSecret: "GOOGLE_CLIENT_SECRET",
callbackURL: "http://localhost:3000/auth/google/callback"
},
(accessToken, refreshToken, profile, cb) => {
return cb(err, profile);
}
));

恭喜你這樣子就完成了 Google 登入,而你也可以試著進入 http://localhost:3000/auth/google 試試看囉。

範例程式碼