在Firebase云函数中实现Google OAuth:如何通过access_token识别用户
我已经让整体流程正常运行,但遇到一个核心问题:无法识别用户,没法把令牌保存到对应的Firestore用户对象中。
我用Google API Node.js库,通过OAuth2客户端生成授权URL并设置权限范围,之后重定向用户到该URL,这部分没问题:
const {google} = require('googleapis'); const oauth2Client = new google.auth.OAuth2( YOUR_CLIENT_ID, YOUR_CLIENT_SECRET, YOUR_REDIRECT_URL ); const scopes = [ 'https://www.googleapis.com/auth/calendar' ]; const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (默认) 或 'offline' (获取refresh_token) access_type: 'offline', state: 'state_parameter_gets_passed_back', scope: scopes, // 启用增量授权,推荐最佳实践 include_granted_scopes: true }); console.log('created an authorizationUrl: ' + authorizationUrl); res.redirect(authorizationUrl);
我把一个HTTPS云函数端点设为回调URL,用来接收授权响应。收到响应后能成功获取授权码并兑换令牌,但执行saveUserToken时卡壳了:这个令牌到底属于哪个用户? 我的云函数只负责监听响应,没有用户身份信息:
exports.recieveGoogleCodeFromResponseURL = functions.https.onRequest(async (req, res) => { const code = req.query.code; console.log('got a code, it is:' + code); const url = require('url'); if (req.query.code != null) { let userCredential; console.log('we have a code, trading it for tokens'); let { tokens } = await oauth2Client.getToken(code); console.log({ tokens }); oauth2Client.setCredentials(tokens); // 问题就在这里:要把令牌保存给哪个用户? saveUserToken(tokens, uid); // 保存到Firestore } res.json({result: `Got a response from Google`, code: code, scope: req.query.scope}); });
令牌的响应格式如下:
{ access_token: "longtoken", expiry_date: 166..., refresh_token: "anothertoken", scope: "https://www.googleapis.com/auth/calendar", token_type: "Bearer" }
我知道access_token和refresh_token都不是JWT令牌,没法解码出用户信息。我查的Firebase云函数示例都提到“生产环境要把令牌存到安全的持久化数据库”,我可以用Firestore实现,但就是没法确定回调里的授权码对应哪个用户。
其他平台(比如Instagram、LinkedIn、Twitch)的OAuth示例里,要么兑换授权码的响应直接返回用户ID,要么能用access_token调用API获取用户信息:
- Instagram示例:兑换授权码后直接拿到用户ID
const oauth2 = instagramOAuth2Client(); const results = await oauth2.authorizationCode.getToken({ code: req.query.code, redirect_uri: OAUTH_REDIRECT_URI, }); functions.logger.log('Auth code exchange result received:', results); // 现在我们有了Instagram的access_token和用户身份 const accessToken = results.access_token; const instagramUserID = results.user.id;
- LinkedIn示例:用access_token调用API获取用户信息
const linkedin = Linkedin.init(results.access_token); linkedin.people.me(async (error, userResults) => { if (error) { throw error; } functions.logger.log( 'Auth code exchange result received:', userResults ); // 现在我们有了LinkedIn的access_token和用户身份 const linkedInUserID = userResults.id;
我知道有个Google库可以验证ID令牌,但这次OAuth流程根本没返回ID令牌。我感觉自己漏了个简单的方法,想问:有没有可以传入access_token来识别用户的Google API?
解决方法
你可以通过以下两种方式获取用户身份:
- 调用Google People API获取用户信息
在拿到access_token后,用Google API调用People API的people.get接口,获取用户的基本信息(比如邮箱、用户ID)。需要先在授权范围里加上https://www.googleapis.com/auth/userinfo.email或https://www.googleapis.com/auth/userinfo.profile(根据需求选择)。
示例代码:
const {google} = require('googleapis'); const people = google.people({version: 'v1', auth: oauth2Client}); const userInfo = await people.people.get({ resourceName: 'people/me', personFields: 'emailAddresses,metadata' }); const userEmail = userInfo.data.emailAddresses[0].value; // 之后可以用这个邮箱去Firebase Auth里查找对应的用户UID const userRecord = await admin.auth().getUserByEmail(userEmail); const uid = userRecord.uid; saveUserToken(tokens, uid);
- 利用
state参数传递用户身份
在生成授权URL时,把当前已登录用户的UID加密后放到state参数里(注意要加密防止篡改),回调时从req.query.state里取出解密,就能拿到用户UID。
示例修改:
生成授权URL时:
const uid = "当前登录用户的UID"; // 简单加密(生产环境建议用更安全的加密方式,比如JWT) const encryptedState = Buffer.from(uid).toString('base64'); const authorizationUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', state: encryptedState, scope: [...scopes, 'https://www.googleapis.com/auth/userinfo.email'], include_granted_scopes: true });
回调函数里:
exports.recieveGoogleCodeFromResponseURL = functions.https.onRequest(async (req, res) => { const code = req.query.code; const encryptedState = req.query.state; // 解密 const uid = Buffer.from(encryptedState, 'base64').toString('utf8'); if (code != null) { let { tokens } = await oauth2Client.getToken(code); oauth2Client.setCredentials(tokens); saveUserToken(tokens, uid); } res.json({result: `Got a response from Google`, code: code, scope: req.query.scope}); });
注意:第二种方法需要确保用户在触发授权流程时已经通过Firebase Auth登录,这样你才能拿到用户的UID。如果用户未登录,还是需要用第一种方法通过API获取用户信息,再关联到Firebase用户。
内容的提问来源于stack exchange,提问作者Joshua Dance

