Node.js和vue3實(shí)現(xiàn)GitHub OAuth第三方登錄

前言

第三方登入太常見(jiàn)了徽千,微信,微博珍手,QQ...總有一個(gè)你用過(guò)办铡。

在開發(fā)中,我們希望用戶可以通過(guò)GitHub賬號(hào)登錄我們的網(wǎng)站琳要,這樣用戶就不需要注冊(cè)賬號(hào)寡具,直接通過(guò)GitHub賬號(hào)登錄即可。

效果演示

1.gif

注冊(cè)配置 GitHub 應(yīng)用

1.首先登錄你的GitHub然后點(diǎn)擊右上角的頭像->點(diǎn)擊進(jìn)入Settings頁(yè)面

WX20240907-133241.png

2.在Settings頁(yè)面中點(diǎn)擊左側(cè)邊欄的 Developer settings

WX20240907-141307.png

3.然后點(diǎn)擊OAuth Apps稚补,點(diǎn)擊 Register a new application

一個(gè)用戶或組織最多可以擁有100個(gè)OAuth應(yīng)用童叠。

WX20240907-143111.png

4.填寫應(yīng)用信息

我這邊使用了騰訊翻譯插件,為了照顧英語(yǔ)不好的朋友觀看理解。

WX20240907-144204.png

這里主要是Authorization callback URL的填寫厦坛;

這個(gè)應(yīng)用回調(diào)地址就是上面登錄流程授權(quán)之后返回的redirect_uri五垮。

5.點(diǎn)擊Generate new client secret生成Client Secret

WX20240907-144722.png

6.將Client IDClient Secret復(fù)制到配置文件,用于后面向GitHub發(fā)送請(qǐng)求傳參杜秸。

注意: 只會(huì)出現(xiàn)一次Client secrets,自己保存好

WX20240907-161327.png

前后端調(diào)用流程步驟:

  • 前端:用戶點(diǎn)擊按鈕跳轉(zhuǎn)到GitHub授權(quán)頁(yè)面放仗;
  • 前端:用戶在授權(quán)頁(yè)面同意授權(quán)后,GitHub將用戶重定向到您的網(wǎng)站撬碟;
  • 前端:重定向的URL中包含一個(gè)授權(quán)碼code诞挨,在該頁(yè)面中獲取授權(quán)碼;
  • 前端:調(diào)用后端登錄api呢蛤,將獲取到的code傳給后端惶傻;
  • 后端:后端收到code后調(diào)用GitHub的token api,獲取access_token其障;
  • 后端:獲取到access_token后調(diào)用 user api银室,獲取用戶信息返回給前端;
  • 前端:拿到后端返回的用戶信息后静秆,將用戶信息保存到本地粮揉,完成登錄。

前端 Vue 實(shí)現(xiàn)

1.安裝必要依賴

  • axios是一個(gè)HTTP客戶端庫(kù)抚笔,用于向服務(wù)端發(fā)送請(qǐng)求扶认。
npm install axios

2.替換你的配置信息

// github配置信息
const config = {
  // 替換為你的回調(diào)地址
  redirect_uri: 'http://127.0.0.1:9090/pages/login/login',
  // 替換為你的 client_id
  client_id: 'Ov23li3ZcThmL87YHUBL',
}

3.代碼示例

<template>
  <div class="user-box">
    <div v-if="userInfo.id" class="user-info">
      <img class="user-img" :src="userInfo.avatar_url" />
      <text class="user-name">用戶昵稱:{{ userInfo.name || userInfo.login }}</text>
      <text class="user-openid">nodeId{{ userInfo.node_id }}</text>
    </div>
    <div v-else class="user-empty">
      {{ loading ? '用戶登錄中...' : userInfo?.id ? '用戶已登錄' : '用戶未登錄' }}
    </div>
  </div>
  <button @click="oauth">發(fā)起GitHub授權(quán)</button>
</template>

<script setup>
import { onMounted, ref } from 'vue'
import axios from 'axios'
const loading = ref(false)
let userInfo = ref({})
// github配置信息
const config = {
  // 替換為你的回調(diào)地址
  redirect_uri: 'http://127.0.0.1:9090/pages/login/login',
  // 替換為你的 client_id
  client_id: 'Ov23li3ZcThmL87YHUBL',
}

// 請(qǐng)求后端登錄
function reqLogin(code) {
  console.log('3.將獲取到的code發(fā)送給后端進(jìn)行登錄');
  if (loading.value) return
  loading.value = true
  axios.post('http://127.0.0.1:3000/api/github/login', { code })
    .then(res => {
      // 前端拿到用戶信息后,可以保存到數(shù)據(jù)庫(kù)或者本地殊橙,或者直接跳轉(zhuǎn)到個(gè)人中心頁(yè)面辐宾。
      console.log('4.登錄成功獲取到用戶信息', res.data);
      userInfo.value = res.data
    }).catch(err => {
      console.log('登錄出錯(cuò)了!', err);
    }).finally(() => {
      console.log('finally');
      loading.value = false
    })
}

// 獲取地址欄中的code 獲取不到將返回 null
function getCode() {
  // 獲取當(dāng)前 URL 的查詢參數(shù)
  const urlParams = new URLSearchParams(window.location.search);
  // 從查詢參數(shù)中獲取 'code' 值
  const code = urlParams.get('code');
  if (code) {
    console.log('2.授權(quán)后膨蛮,獲取地址欄中的code');
  }
  return code
}

// 發(fā)起授權(quán)
function oauth() {
  console.log('1.點(diǎn)擊授權(quán)按鈕叠纹,跳轉(zhuǎn)到GitHub授權(quán)頁(yè)中');
  const url = `https://github.com/login/oauth/authorize?client_id=${config.client_id}&redirect_uri=${config.redirect_uri}`
  window.location.href = url
}

onMounted(() => {
  const code = getCode()
  if (code) reqLogin(code)
})
</script>

<style scoped lang="scss">
.user-box {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 20px;

  .user-info {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;

    .user-img {
      width: 60px;
      height: 60px;
      border-radius: 99px;
      border: 1px solid black;
    }
  }

  .user-empty {
    // background-color: #f5f6f7;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100px;
    height: 100px;
    border: 1px solid black;
    border-radius: 6px;
  }
}
</style>

后端 Node.js 實(shí)現(xiàn)

1.安裝必要依賴

  • express是一個(gè)Web應(yīng)用框架,用于構(gòu)建Web應(yīng)用敞葛。
  • cors是一個(gè)中間件誉察,用于處理跨域請(qǐng)求。
  • axios是一個(gè)HTTP客戶端庫(kù)惹谐,用于向服務(wù)端發(fā)送請(qǐng)求持偏。
npm install express cors axios 

2.替換你的配置信息

// github配置信息
const githubConfig = {
  // 替換為你的回調(diào)地址
  redirect_uri: 'http://127.0.0.1:9090/pages/login/login',
  // 替換為你的 client_id
  client_id: 'Ov23li3ZcThmL87YHUBL',
  // 替換為你的 client_secret
  client_secret: 'e021cb59a650476e62be3ee72fc9686e2c86c1d3',
}

3.代碼示例

// Node.js和vue3實(shí)現(xiàn)GitHub OAuth第三方登錄
const express = require('express'); // 導(dǎo)入 Express 模塊
const cors = require('cors'); // 導(dǎo)入 CORS 模塊,用于處理跨域請(qǐng)求
const axios = require('axios'); // 導(dǎo)入 Axios 模塊氨肌,用于發(fā)起 HTTP 請(qǐng)求

const app = express(); // 創(chuàng)建 Express 應(yīng)用實(shí)例
app.use(cors()); // 使用 CORS 中間件解決跨越請(qǐng)求
app.use(express.json()) // 解析 json 格式請(qǐng)求體
app.use(express.urlencoded({ extended: true })) // 解析傳統(tǒng)表單請(qǐng)求體

// github配置信息
const githubConfig = {
  // 替換為你的回調(diào)地址
  redirect_uri: 'http://127.0.0.1:9090/pages/login/login',
  // 替換為你的 client_id
  client_id: 'Ov23li3ZcThmL87YHUBL',
  // 替換為你的 client_secret
  client_secret: 'e021cb59a650476e62be3ee72fc9686e2c86c1d3',
}

// github登錄
app.post('/api/github/login', async (req, res) => {
  // 1鸿秆、校驗(yàn)必填參數(shù)
  if (!req.body.code) {
    throw new Error('必填參數(shù)不能為空!')
  }
  // 2怎囚、獲取 Access token
  const accessTokenInfo = await getAccessToken(req.body.code)
  // 3卿叽、獲取用戶信息
  const userInfo = await getUserInfo(accessTokenInfo.access_token)
  // 4、在這步你可以將用戶信息存入數(shù)據(jù)庫(kù)中等其他操作,這里我直接返回了
  res.status(200).send(userInfo)
})

// 獲取 access_token
async function getAccessToken(code) {

  //官方文檔: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps

  // 1考婴、驗(yàn)證后端傳來(lái)的code
  if (!code || code.length !== 20) {
    throw new Error('code參數(shù)不正確贩虾!');
  }

  // 2、向github發(fā)送post請(qǐng)求蕉扮,成功的話會(huì)整胃,response.data里面有一個(gè)access_token
  const response = await axios({
    method: 'post',
    url: 'https://github.com/login/oauth/access_token',
    params: {
      redirect_uri: githubConfig.redirect_uri,
      client_id: githubConfig.client_id,
      client_secret: githubConfig.client_secret,
      code,
    },
    headers: { 'accept': 'application/json' },
  });

  if (!response.data?.access_token) {
    throw new Error('獲取 access_token 失敗喳钟!' + JSON.stringify(response.data))
  }

  // response.data:{
  //   "access_token":"gho_16C7e42F292c6912E7710c838347Ae178B4a",
  //   "scope":"repo,gist",
  //   "token_type":"bearer"
  // }

  return response.data
}


// 獲取用戶信息
async function getUserInfo(access_token) {
  //官方文檔: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#3-use-the-access-token-to-access-the-api
  const response = await axios({
    method: "get",
    url: 'https://api.github.com/user',
    headers: {
      Authorization: `Bearer ${access_token}`,
    },
  });

  if (!response.data?.id) throw new Error('獲取用戶信息失敗在岂!')

  /**
  response.data = {
    "login": "China-quanda",
    "id": 36378336,
    "node_id": "MDQ6VXNlcjM2Mzc4MzM2",
    "avatar_url": "https://avatars.githubusercontent.com/u/36378336?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/China-quanda",
    "html_url": "https://github.com/China-quanda",
    "followers_url": "https://api.github.com/users/China-quanda/followers",
    "following_url": "https://api.github.com/users/China-quanda/following{/other_user}",
    "gists_url": "https://api.github.com/users/China-quanda/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/China-quanda/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/China-quanda/subscriptions",
    "organizations_url": "https://api.github.com/users/China-quanda/orgs",
    "repos_url": "https://api.github.com/users/China-quanda/repos",
    "events_url": "https://api.github.com/users/China-quanda/events{/privacy}",
    "received_events_url": "https://api.github.com/users/China-quanda/received_events",
    "type": "User",
    "site_admin": false,
    "name": "Quanda",
    "company": null,
    "blog": "",
    "location": "北京",
    "email": null,
    "hireable": null,
    "bio": null,
    "twitter_username": null,
    "notification_email": null,
    "public_repos": 6,
    "public_gists": 0,
    "followers": 0,
    "following": 2,
    "created_at": "2018-02-11T17:35:16Z",
    "updated_at": "2024-09-07T10:31:22Z"
  }
  */

  return response.data;
}

// 啟動(dòng)服務(wù)
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on http://127.0.0.1:${PORT}`);
});

總結(jié)

  1. 首先奔则,在github上注冊(cè)一個(gè)應(yīng)用,并配置好回調(diào)地址蔽午,獲取client_idclient_secret易茬。
  2. 在前端頁(yè)面上,通過(guò)點(diǎn)擊發(fā)起GitHub授權(quán)按鈕及老,替換當(dāng)前地址為https://github.com/login/oauth/authorize抽莱,攜帶上我們前面獲取的client_id和回調(diào)地址。
  3. github會(huì)返回一個(gè)code骄恶,這個(gè)code是臨時(shí)的食铐,我們通過(guò)這個(gè)code向github請(qǐng)求access_token,再通過(guò)access_token向github請(qǐng)求用戶信息僧鲁。
  4. 最后虐呻,將用戶信息返回給前端,前端拿到用戶信息后寞秃,可以保存到數(shù)據(jù)庫(kù)或者本地斟叼,或者直接跳轉(zhuǎn)到個(gè)人中心頁(yè)面。
  5. 注意春寿,這個(gè)項(xiàng)目只是演示如何實(shí)現(xiàn)github登錄朗涩,實(shí)際應(yīng)用中,需要做更多的處理绑改,比如用戶注冊(cè)谢床,用戶信息保存等。
  6. 示例代碼僅供參考绢淀,實(shí)際應(yīng)用中萤悴,需要根據(jù)具體的業(yè)務(wù)需求進(jìn)行修改。
  7. 示例代碼中皆的,沒(méi)有做任何的錯(cuò)誤處理覆履,實(shí)際應(yīng)用中,需要做錯(cuò)誤處理。

我們發(fā)現(xiàn)第三方登錄的流程其實(shí)都差不多硝全,差別就是不同的平臺(tái)栖雾,和自己應(yīng)用的業(yè)務(wù)會(huì)有點(diǎn)不一樣。所以呢伟众,在做之前先要理清思路析藕,仔細(xì)看文檔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凳厢,一起剝皮案震驚了整個(gè)濱河市账胧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌先紫,老刑警劉巖治泥,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異遮精,居然都是意外死亡居夹,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門本冲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)准脂,“玉大人,你說(shuō)我怎么就攤上這事檬洞±旮啵” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵疮胖,是天一觀的道長(zhǎng)环戈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)澎灸,這世上最難降的妖魔是什么院塞? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮性昭,結(jié)果婚禮上拦止,老公的妹妹穿的比我還像新娘。我一直安慰自己糜颠,他們只是感情好汹族,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著其兴,像睡著了一般顶瞒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上元旬,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天榴徐,我揣著相機(jī)與錄音守问,去河邊找鬼。 笑死坑资,一個(gè)胖子當(dāng)著我的面吹牛耗帕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播袱贮,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼仿便,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了攒巍?” 一聲冷哼從身側(cè)響起嗽仪,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窑业,沒(méi)想到半個(gè)月后钦幔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡常柄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了搀擂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片西潘。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖哨颂,靈堂內(nèi)的尸體忽然破棺而出喷市,到底是詐尸還是另有隱情,我是刑警寧澤威恼,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布品姓,位于F島的核電站,受9級(jí)特大地震影響箫措,放射性物質(zhì)發(fā)生泄漏腹备。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一斤蔓、第九天 我趴在偏房一處隱蔽的房頂上張望植酥。 院中可真熱鬧,春花似錦弦牡、人聲如沸友驮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)卸留。三九已至,卻和暖如春椭豫,著一層夾襖步出監(jiān)牢的瞬間耻瑟,已是汗流浹背旨指。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匆赃,地道東北人淤毛。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像算柳,于是被迫代替她去往敵國(guó)和親低淡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容