无需Passport.js等库,在Firebase Cloud Functions实现LinkedIn登录
在Firebase Cloud Functions中实现LinkedIn登录认证(无需Passport.js)
完全可以不用Passport.js这类第三方库,直接基于LinkedIn的OAuth2流程结合Firebase Admin SDK实现认证。核心逻辑是:前端引导用户完成LinkedIn授权拿到授权码,将授权码传给Cloud Functions,后端用授权码兑换access_token、获取用户信息,最后生成Firebase自定义token返回给前端完成登录。
具体步骤分解
1. 前端(React)处理授权跳转
构造LinkedIn授权URL,引导用户跳转完成授权,授权后LinkedIn会携带code参数重定向到你指定的页面。
const handleLinkedInLogin = () => { const clientId = "你的LinkedIn客户端ID"; const redirectUri = "https://你的应用域名/linkedin-callback"; const scope = "r_liteprofile r_emailaddress"; // 必须申请的权限 const authUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scope)}`; window.location.href = authUrl; };
2. 前端回调页获取授权码并传给云函数
在重定向目标页面中提取URL里的code,通过POST请求发送到你的Cloud Functions接口。
import axios from 'axios'; import { getAuth, signInWithCustomToken } from "firebase/auth"; // 在回调页面的useEffect中处理 useEffect(() => { const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); if (code) { axios.post('/api/linkedin-auth', { code }) .then(res => { // 用Firebase自定义token完成登录 const auth = getAuth(); signInWithCustomToken(auth, res.data.token) .then(() => { // 登录成功,跳转到主页 window.location.href = '/'; }); }) .catch(err => console.error('认证失败:', err)); } }, []);
3. Cloud Functions后端处理授权流程
注意:Cloud Functions环境中发起HTTP请求,推荐使用node-fetch而非axios(避免环境兼容问题),先执行npm install node-fetch安装依赖。
const functions = require('firebase-functions'); const admin = require('firebase-admin'); const fetch = require('node-fetch'); admin.initializeApp(); // 配置LinkedIn密钥(通过firebase functions:config:set linkedin.client_id="xxx" linkedin.client_secret="xxx"设置) const LINKEDIN_CLIENT_ID = functions.config().linkedin.client_id; const LINKEDIN_CLIENT_SECRET = functions.config().linkedin.client_secret; const REDIRECT_URI = "https://你的应用域名/linkedin-callback"; exports.linkedinAuth = functions.https.onRequest(async (req, res) => { if (req.method !== 'POST') { return res.status(405).send('仅支持POST请求'); } const { code } = req.body; if (!code) { return res.status(400).send('缺少授权码'); } try { // 1. 用授权码兑换access_token const tokenRes = await fetch('https://www.linkedin.com/oauth/v2/accessToken', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', code, client_id: LINKEDIN_CLIENT_ID, client_secret: LINKEDIN_CLIENT_SECRET, redirect_uri: REDIRECT_URI }) }); const tokenData = await tokenRes.json(); if (!tokenData.access_token) throw new Error('获取access_token失败'); // 2. 获取用户基本信息 const profileRes = await fetch('https://api.linkedin.com/v2/me', { headers: { 'Authorization': `Bearer ${tokenData.access_token}` } }); const profileData = await profileRes.json(); // 3. 获取用户邮箱 const emailRes = await fetch('https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))', { headers: { 'Authorization': `Bearer ${tokenData.access_token}` } }); const emailData = await emailRes.json(); const userEmail = emailData.elements[0]['handle~'].emailAddress; // 4. 创建或获取Firebase用户 let userRecord; try { userRecord = await admin.auth().getUserByEmail(userEmail); } catch (err) { // 用户不存在则创建新用户 userRecord = await admin.auth().createUser({ email: userEmail, displayName: `${profileData.localizedFirstName} ${profileData.localizedLastName}`, photoURL: profileData.profilePicture?.displayImage~?.elements[0]?.identifiers[0]?.identifier }); } // 5. 生成Firebase自定义token返回给前端 const customToken = await admin.auth().createCustomToken(userRecord.uid); res.status(200).json({ token: customToken }); } catch (err) { functions.logger.error('LinkedIn认证出错:', err); res.status(500).send('认证流程失败'); } });
常见问题排查
- Cloud Functions请求报错:检查是否安装了
node-fetch,LinkedIn的client ID/secret是否通过Firebase配置正确,请求头和参数是否符合LinkedIn OAuth2规范。 - CORS问题:在云函数开头添加CORS处理逻辑,或使用
cors库解决跨域。 - 权限不足:确保在LinkedIn开发者平台已申请
r_liteprofile和r_emailaddress权限,且redirect URI已正确配置。
内容的提问来源于stack exchange,提问作者Zohaib Shaheen
相关产品推荐
相关产品推荐

