最近在實現手邊專案的 Facebook 登入需求時,原先使用了 nuxt/auth 作為認證,但基於一些流程上的衝突,最後索性放棄並改由自建第三方登入流程。這篇文會使用 nuxt.js 一步步從 Facebook 使用者登入到拿到使用者資料,主要整理給使用 nuxt/auth 或任何認證函式庫上碰到問題的開發者,希望我的筆記能夠讓你輕鬆實作出 Facebook 登入。
準備作業
前置的準備作業,我們需要:
-一個乾淨能輕鬆實現 Best Practice 的 Nuxt.js 專案
-一個 Heroku 帳號,用來佈署 nuxt.js App
-一個 Facebook 帳號,用來建立應用程式
這篇筆記會使用 Nuxt.js 中的 ServerMiddleware 並配合 Express.js 來實作。
建立專案
首先,我們需要一個乾淨的 nuxt.js 專案,
npx create-nuxt-app <your-app-name>
設定檔的部分可直接參考上圖,比較重要的是 Nuxt.js modules 一定要安裝 axios,Rendering mode, Deplyment target 分別要選擇 Universal 及 Server。
接下來,我們安裝 Express.js, dotenv 及 qs:
npm install express
npm install qs
npm install dotenv
接著可以移除 Components 中不必要的檔案,保留首頁和 components/Home.vue,並新增 pages/auth/facebook/callback/index.vue ,內容可參考:
// pages/index.vue
<template>
<Home />
</template><script>
export default {};
</script>// components/Home.vue
<template>
<div>
<h1>Welcome to your nuxt app</h1>
<button>Facebook Login</button>
</div>
</template><script>
export default {
name: "Home"
};
</script>// pages/auth/facebook/callback/index.vue
<template>
<div>請稍候...</div>
</template><script>
export default {
name: "AuthFacebookCallback"
};
</script>
建立 Heroku 環境
關於在本地使用加密連線環境,也可使用 mkcert 輕鬆實現,詳細可參考我的這篇記錄。
由於 Facebook 登入需在加密連線環境下進行,因此我們需要一個帶有加密連線的環境。Heroku 是一個支援多種程式語言的雲平台即服務,選擇 Heroku 作為佈署工具的原因很簡單:因為他相對 GCP 或其他服務超級方便又簡單,在嘗試 GCP 佈署後才知道原來 Heroku 背後為我們做了非常多的事情!由於註冊流程較冗長,如果你沒有 Heroku 帳號,可參考神Q超人的 Heroku | 搭配 Git 在 Heroku 上部署網站的手把手教學。
完成 Heroku 註冊後,可於專案目錄開啟終端機,輸入:
# 這邊的指令說明可參考 https://nuxtjs.org/docs/2.x/deployment/heroku-deployment
heroku login
heroku create <your-app-name>
heroku buildpacks:set heroku/nodejs
heroku config:set HOST=0.0.0.0
接著 commit 所有更動,並推上 heroku:
git add .
git commit -m "init"
git push heroku master
完成後輸入 heroku open,應該會看到網站:
看到成功畫面就代表完成了。
建立 Facebook 應用程式
相信多數人都有 Facebook 帳號了,這篇文就不再贅述如何註冊 Facebook 帳號,我們可以到 https://developers.facebook.com/apps/?show_reminder=true 來註冊一個應用程式。點選右方的「建立應用程式」,並選擇「企業商家」。
成功後應該會進入應用程式的頁面。點選左側設定>基本資料可進入到基本資料頁。在「應用程式網域」中填寫在 Heroku 的網域:
捲動到最下方點選新增平台,選擇 WEB 並一樣輸入網址。完成後點選儲存變更。
點選左側欄的「新增商品」,並選擇 Facebook 登入。成功的話應該會被導至「快速入門」,選擇「網站」,並在網站網址填寫 Heroku 上的網域,按下 Save,接著點選左側欄 Facebook 登入的「設定」。
透過這篇文的實作是使用 Graph API V11.0,存取權限需要手動開啟,因此我們點選警告框框中的「Get Advanced Access」。在頁面中,可點選 public_profile 及 email 的「申請進階存取權限」,輸入密碼後,便可切換為 Advanced Access。
最後回到左側欄的 Facebook 登入,點選設定,在有效的 OAuth 重新導向 URI 中,輸入如下網址:
https://your-heroku-domain/auth/facebook/callback
前置作業的部分大功告成!
實作登入
步驟一、讓使用者進入 Facebook 登入頁面
Facebook 登入所使用的連結為 https://www.facebook.com/v11.0/dialog/oauth ,其中需於後方夾帶一些參數方便 Facebook 辨識開發者的需求,其中包含:
client_id — Facebook 應用程式的應用程式編號
redirect_uri — 完成 Facebook 登入後,應該夾帶資料導回的頁面。以我們手作的範例來說,是 https://your-heroku-domain/auth/facebook/callback
scope — 開發者需要和使用者索取的資料,以我們手作的範例來說,我們會取得 email 及 public_profile
response_type — 重導向夾帶的回應資料種類,以我們手作的範例來說,我們會採用「code」
在開始作業前,我們將 client_id 存入專案的 .env 環境中。到 Facebook 設定>基本資料中,複製應用程式編號。
於專案根目錄建立 .env 檔案,加入 FB_CLIENT_ID 及完成登入後重導向的網址:
FB_CLIENT_ID=xxxxxxxxxxxx
FB_REDIRECT_URI=https://your-heroku-domain/auth/facebook/callback
至 nuxt.config.js,導入 env:
// nuxt.config.js
require("dotenv").config();
export default {
// ...
env: {
FB_CLIENT_ID: process.env.FB_CLIENT_ID || "",
FB_REDIRECT_URI: process.env.FB_REDIRECT_URI || ""
}
}
需要的參數都準備好了,接著我們可以來準備為登入按鈕加入所需參數了。至 components/Home.vue,貼上以下程式碼:
<template>
<div>
<h1>Welcome to your nuxt app</h1>
<a :href="facebookLoginUrl">
<button>Facebook Login</button>
</a>
</div>
</template><script>
import qs from "querystring";
export default {
name: "Home",
data() {
return {
facebookLoginUrl: ""
};
},
mounted() {
this.setFacebookLoginUrl();
},
methods: {
setFacebookLoginUrl() {
const FB_DIALOG_LINK = "https://www.facebook.com/v11.0/dialog/oauth";
const params = qs.stringify({
client_id: process.env.FB_CLIENT_ID,
redirect_uri: process.env.FB_REDIRECT_URI,
scope: ["public_profile", "email"].join(","),
response_type: "code"
});this.facebookLoginUrl = `${FB_DIALOG_LINK}?${params}`;
}
}
};
</script>
接著我們就可以先推一版上 Heroku 確認是否能導回網站。首先我們須將 .env 的設定同步至 Heroku。在專案目錄開啟終端機,輸入:
heroku config:set FB_CLIENT_ID=xxxxxxxxxxxx
heroku config:set FB_REDIRECT_URI=https://your-heroku-domain/auth/facebook/callback
接著可以直接推一版:
git add .
git commit -m "step1"
git push heroku master
上線後,可進入網站直接點擊 Facebook Login,輸入開發者帳密後,成功的話會顯示以下畫面:
顯示這畫面代表步驟一已經成功。
步驟二、在前端伺服器建立一個 Server Middleware
回到 nuxt.config.js,我們增加一個 serverMiddleware,在這個實例中,我將使用 POST /facebook/login 作為接口:
serverMiddleware: [
{ path: '/facebook/login', handler: '~/server/facebookAuth.js' },
],
於根目錄增加 /server/facebookAuth.js:
const express = require("express");
const app = express();
require("dotenv").config();app.post("/", async (req, res) => {
try {
if (!req.query.code) {
return res.json("no code found");
}
return res.json(req.query.code)
} catch (error) {
console.log("[Facebook Login API Error]", error);
return res.json("Error occured");
}
});module.exports = app;
當客戶端對 POST /facebook/login 發出請求時,將會觸發 app.post 中的函式,我們可以實際發送請求看看:
// /pages/auth/facebook/callback/index.vue
<template>
<div>請稍候...</div>
</template><script>
export default {
name: "AuthFacebookCallback",
async mounted() {
const { code } = this.$route.query;
const { data } = await this.$axios.post(
`${location.origin}/facebook/login?code=${code}`
);
console.log(data);
}
};
</script>
實際連線至 http://localhost:3000/auth/facebook/callback?code=ismycodesenttoserver ,應該可以看到:
若有成功顯示 ismycodesenttoserver,表示 middleware 串接成功。
步驟三、透過 Facebook 回傳的 code 取得 Facebook 的 Access Token
取得 code 後,我們可以會在這一頁夾帶 code 對剛剛建立的 server middleware 發出請求,讓伺服器去完成剩餘的請求。
之所以這樣做,是為了避免 access_token 及後續的請求(例如需要夾帶 Facebook 資料去和後端伺服器交換資料)暴露在客戶端中,我們希望只要夾帶 code,後續的請求都可以藉由前端伺服器接應完成,最後僅單純回傳使用者登入所需的資訊。
接下來我們實作藉由 code 取得 access_token,Facebook 取得 access_token 的 API 為 https://graph.facebook.com/v11.0/oauth/access_token,其中需夾帶以下參數:
-client_id — 應用程式編號
-client_secret — 應用程式密鑰
-redirect_uri — 與我們發請求取得code時相同的redirect_uri
-code — 在上一個步驟中取得的code
密鑰可以於 Facebook 設定>基本資料中取得。這是非常敏感不得外洩的資料,因此請務必寫在環境變數中。
取得密鑰後,可於 .env 中設定:
FB_CLIENT_SECRET=xxxx
並且同步於 heroku 環境變數:
heroku config:set FB_CLIENT_SECRET=xxxxxxxx
了解參數組成後,我們就可以撰寫取得 Access Token 的函式:
async function getAccessTokenFromCode(code) {
const params = qs.stringify({
client_id: process.env.FB_CLIENT_ID,
client_secret: process.env.FB_CLIENT_SECRET,
redirect_uri: process.env.FB_REDIRECT_URI,
code
});
const { data } = await axios.get(
`https://graph.facebook.com/v11.0/oauth/access_token?${params}`
);
return data.access_token;
}
並呼叫他:
// 記得導入需要的套件(如下)
const express = require("express");
const app = express();
import axios from "axios";
import qs from "querystring";
require("dotenv").config();app.post("/", async (req, res) => {
try {
if (!req.query.code) {
return res.json("no code found");
}
const accessToken = await getAccessTokenFromCode(req.query.code);
return res.json({ accessToken });
} catch (error) {
console.log("[Facebook Login API Error]", error);
return res.json("Error occured");
}
});
實際推上 Heroku,在嘗試一次,應該可以取得 Access Token:
已經快完成了!
步驟四、透過 Facebook 的 Access Token 取得使用者資料
安全地取得 Access Token 後,我們就能透過 Access Token 與 Facebook 交換資料,這一步一樣會在伺服器端完成,確保客戶端從來都不會知道 Acess Token。
Facebook 取得使用者資料的 API 為 https://graph.facebook.com/me,所需參數如下:
-access_token — 我們稍早透過 code 取得的資料
-fields — 我們需要與 Facebook 取得的登入者資料。此範例我們將取得使用者姓名、email 及大頭貼。
得知參數後,我們可以直接著手撰寫請求函式:
async function getFacebookUserData(access_token) {
const params = qs.stringify({
fields: ["id", "email", "first_name", "last_name"].join(","),
access_token
});
const { data } = await axios.get(`https://graph.facebook.com/me?${params}`);
return {
email: data.email,
id: data.id,
name: data.last_name + data.first_name
};
}
在 POST 的回呼函式中,於取得 access_token 後呼叫其取得使用者資料:
app.post("/", async (req, res) => {
try {
if (!req.query.code) {
return res.json("no code found");
}
const accessToken = await getAccessTokenFromCode(req.query.code);
const userData = await getFacebookUserData(accessToken);
return res.json({ userData });
} catch (error) {
console.log("[Facebook Login API Error]", error);
return res.json("Error occured");
}
});
佈署成功後,嘗試登入,應該可以成功取得自己的資料:
接下來呢?
在取得使用者資料後,接下來就是依開發需求去做其他事情。以我目前手邊的案例來說,取得使用者資料後伺服器還會發一次請求給後端伺服器交換使用者資料,並將所需資料寫入瀏覽器的存儲中,而實際如何使用,就要端看開發的情境及需求了。
結語
第三方登入實作算是我花蠻長時間才真正吸收的,從最早直接用客戶端實作、到後端協助,到最後使用前端伺服器實作,一路改變下來也算是學習良多,希望這篇文可以幫助到你,如果內文有任何錯誤,也煩請不吝指出,謝謝大家,最後附上完整程式碼。
References:
延伸閱讀: