Andy的前後端技術筆記、生活紀錄
google_oauth2_flow
發佈於: 2024-01-27 更新於: 2024-04-21 分類於: frontend

google oauth2 的流程

一般人視角

以一般人視角來思考oauth2的驗證的流程,在一個網頁點擊登入頁面,選擇google登入,
會跳出一個是否要同意網站取用你的google資料用來登入,點擊同意後,就會回到該網站的頁面(會員中心、首頁…等等)

開發者的流程

前端要有一個按鈕,在點擊後需要被導頁到google的授權頁面,當同意授權後被導回到網站。
在這裡會有前端跟後端的身份,

  1. 當前端點擊google登入的按鈕時會發送一個API query到後端,
  2. 後端依照該網站設定的API憑證的Client ID、Client secret、redirectURL
    建立一個導頁的URL(包含該網站要存取哪些資料的設定),傳送給前端
  3. 當前端收到該URL就會跳到該網址,也就是我們常看到的授權同意的頁面,當使用者點擊同意後,就會拿依照前面提到的 redirectURL去做重新導向
  4. 這個重新導向會在URL的參數中帶上授權碼(是authorization code,不是Access token),而這個導向通常會是導到後端。
  5. 後端拿到授權碼後,就可以向google索取該使用者的Access token(這是有過期時效的),拿到後就可以取得該使用者的資料
  6. 拿到使用者資料後就可以在後端建立使用者的帳號並存入資料庫,若是已經存在就會取得相對應的資料並回傳給前段並且重新導向把前端瀏覽器導向到網站的會員中心或是首頁…等等
    google 的流程圖

要做哪些設定?

  • google Console API 可以去這裡設定Oauth同意畫面以及憑證,設定完之後就可以看到 Client ID、Client secret
  • 已授權的重新導向 URI 要設定第三步的redirectURL,如果在此沒有設定,你在code裡面輸入URL都會被阻擋,會被Google視為非法的導向,redirectURL是要導向後端的路由,是用來接收 authorization code ,收到後可以再去取得 Access token 進一步取得使用者資料,然後後端會再回傳一個網站的重新導向,把使用者導向到該去的網頁(例如會員中心、首頁…等等)
  • Scopes 是用來告知 google 這個網站要取得使用者什麼資料,這也會在同意畫面讓使用者看到,例如取用使用者個人資料、google drive資料等等,這個是在上述的步驟二設定 參考資料
    如果只是要取得Profile 可以設定這兩個就好
    1
    2
    https://www.googleapis.com/auth/userinfo.email	
    https://www.googleapis.com/auth/userinfo.profile
程式碼

npm & github 上都有google的lib可以看並且使用,如果不知道ChatGPT也可以告訴你怎麼寫,但我覺得最主要的還是要搞懂整個流程,如果不懂流程真的會一頭霧水,不知道到底該拿什麼資料。
官方Server端的教學node.js auth API(這兩個連結都是講同一件事,不要頭暈)

如果看不懂要怎麼寫,最下面有做註解,可以參考拿去用。

取得 Access Token 後的Google API

當取得Access Token後是另一個重頭戲,也就是要存取google相關服務,以版主個人來說是希望取得使用者的profile用來註冊,讓使用者不必再次填寫資料。

Google API 可以看一下有哪些API可以用,如果希望獲取使用者資料是使用 people API,點進去後可以看到 API 的 Endpoint要怎麼寫。

Node.js Google API

這邊有教學範例(?),但好像不是很明白XD
Node.js Google API Github -> Using the client library

取得使用者資料範例

1
2
3
4
5
6
7
8
9
const people = google.people({ version: 'v1', auth: oAuth2Client });
// 取得user profile
//參數要怎麼寫可以參考上面的 Google API的Endpoint,還是看不懂,可以請ChatGPT給你一個範例
const me = await people.people.get({
resourceName: 'people/me',
personFields: 'emailAddresses,names,photos',
});

console.log(me.data);

people.get的參數要怎麼放?
可以在這邊的API explorer Google API看一下,點擊右邊的Try it。
gogle-api
下圖可以看到要放什麼參數,真的不知道,可以找GPT協助你XDD,我自己也是看到暈頭
gogle-api-try-it

如果不知道版本,以people為例,可以使用這個函式

1
2
3
4
const {google} = require('googleapis');
const apis = google.getSupportedAPIs();
console.log(apis);
// 他就會印出所有可用的API的版本,你再依照版本號填入即可

或是看文件裡v1,v2來判斷

如果要存取people,server會叫你去開啟這個api服務,如果沒開啟會拒絕你的請求,
只要去這裡 google Console API已啟用的API與服務 去啟動people api 服務,才能順利取得使用者資料

還是看不懂?,可以參考下面的註解👇👇👇👇👇 (我自己都寫到昏頭了XDD)

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
const http = require('http');
const https = require('https');
const url = require('url');
const { google } = require('googleapis');



// ##### 把在 google console api 的 Client ID、Client secret 以及在使用者點擊授權後要導向的頁面 redirectURL 填入這裡
const oauth2Client = new google.auth.OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);

// Access scopes for read-only Drive activity.
// 預計取得的google服務,google drive 的資料。
const scopes = [
'https://www.googleapis.com/auth/drive.metadata.readonly'
];


// 建立一個授權的導向URL,就是讓使用者可以看到授權頁面
// Generate a url that asks permissions for the Drive activity scope
const authorizationUrl = oauth2Client.generateAuthUrl({
// 'online' (default) or 'offline' (gets refresh_token)
access_type: 'offline',
scope: scopes,
include_granted_scopes: true
});
//這邊以下就是看每個人怎麼做,如果用express的話,可以建立對應的路由把剛剛上面的授權的導向URL回傳給使用這讓把使用者導到google的授權頁面。
let userCredential = null;

async function main() {
const server = http.createServer(async function (req, res) {

if (req.url == '/') {
// 在這裡回傳(把授權的導向URL回傳給使用者)
res.writeHead(301, { "Location": authorizationUrl });
}


// 開另外一個路由,當使用者授權後會依照YOUR_REDIRECT_URL路由發請求到後端,要在這裡接
if (req.url.startsWith('/oauth2callback')) {
// Handle the OAuth 2.0 server response
let q = url.parse(req.url, true).query;

if (q.error) { // An error response e.g. error=access_denied
console.log('Error:' + q.error);
} else {
// q.code 就是 authorization code
// 再透過他取得access token
let { tokens } = await oauth2Client.getToken(q.code);
// 把token存到oauth2Client
oauth2Client.setCredentials(tokens);

userCredential = tokens;

// 這裡透過oauth2Client取得使用者相關資料,這裡是取得drive的資料
const drive = google.drive('v3');
drive.files.list({
auth: oauth2Client,
pageSize: 10,
fields: 'nextPageToken, files(id, name)',
}, (err1, res1) => {
if (err1) return console.log('The API returned an error: ' + err1);
const files = res1.data.files;
if (files.length) {
console.log('Files:');
files.map((file) => {
console.log(`${file.name} (${file.id})`);
});
} else {
console.log('No files found.');
}
});
}
}
// 這邊是撤銷token 用在登出時
if (req.url == '/revoke') {
// Build the string for the POST request
let postData = "token=" + userCredential.access_token;

// Options for POST request to Google's OAuth 2.0 server to revoke a token
let postOptions = {
host: 'oauth2.googleapis.com',
port: '443',
path: '/revoke',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};

// Set up the request
const postReq = https.request(postOptions, function (res) {
res.setEncoding('utf8');
res.on('data', d => {
console.log('Response: ' + d);
});
});

postReq.on('error', error => {
console.log(error)
});

// Post the request with data
postReq.write(postData);
postReq.end();
}
res.end();
}).listen(80);
}
main().catch(console.error);
--- 到底拉 The End ---