Firebase Auth用户登录Budibase方案咨询:简化自定义OIDC提供商可行吗?
问题背景
我们正在使用Budibase开发一款内部应用,该应用需与要求在Authorization头中携带Firebase Authentication令牌的REST API交互。Budibase支持为每个API调用设置该请求头,但目前没有内置的Firebase Auth登录方式。我们可选择的登录方式为Google或OpenID Connect,因此想咨询:
- 能否将Firebase Auth作为OIDC提供商使用(而非像Identity Platform那样作为OIDC客户端)?
- 已知答案是否定的,我们调研了使用Node.js的
oidc-provider搭建自定义OIDC提供商以衔接Firebase与Budibase的方案,但该方案包含大量我们不需要的OIDC功能,过于复杂。请问在不影响安全性的前提下,若精简功能(例如仅支持单个可硬编码/通过环境变量配置的客户端),是否有更简便的搭建方式?
解决方案
首先明确:Firebase Auth无法直接作为OIDC身份提供商
Firebase Auth的定位是OIDC客户端(对接Google、Facebook等第三方身份源),本身并不暴露OIDC标准的授权、令牌等端点,因此必须搭建中间层来衔接Firebase Auth与Budibase的OIDC登录需求。
方案1:基于Express的极简OIDC代理(仅支持单个客户端)
只实现Budibase所需的OIDC核心流程,砍掉所有冗余功能,核心逻辑如下:
- 仅允许预配置的Budibase客户端接入(通过环境变量硬编码客户端ID/密钥)
- 复用Firebase Auth的登录流程,验证用户身份后生成符合OIDC标准的令牌返回给Budibase
- 仅实现OIDC授权码流必需的3个端点:授权端点、令牌端点、用户信息端点
简化代码示例
const express = require('express'); const jwt = require('jsonwebtoken'); const admin = require('firebase-admin'); const app = express(); app.use(express.json()); // 环境变量配置(生产环境用环境变量,不要硬编码) const CONFIG = { BUDIBASE_CLIENT_ID: process.env.BUDIBASE_CLIENT_ID, BUDIBASE_CLIENT_SECRET: process.env.BUDIBASE_CLIENT_SECRET, BUDIBASE_REDIRECT_URI: process.env.BUDIBASE_REDIRECT_URI, JWT_SIGNING_KEY: process.env.JWT_SIGNING_KEY, // 生产环境建议用RSA私钥 FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID }; // 初始化Firebase Admin SDK admin.initializeApp({ credential: admin.credential.applicationDefault(), projectId: CONFIG.FIREBASE_PROJECT_ID }); // 1. OIDC授权端点:跳转到自定义Firebase登录页 app.get('/auth', (req, res) => { const state = req.query.state || ''; // 返回嵌入Firebase Web SDK的登录页面 res.send(` <html> <body> <script src="https://www.gstatic.com/firebasejs/9.23.0/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/9.23.0/firebase-auth.js"></script> <script> const firebaseConfig = { /* 你的Firebase Web配置 */ }; firebase.initializeApp(firebaseConfig); async function login() { const provider = new firebase.auth.GoogleAuthProvider(); const result = await firebase.auth().signInWithPopup(provider); const idToken = await result.user.getIdToken(); // 登录成功后回调到代理的回调端点 fetch('/callback', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ firebaseIdToken: idToken, state: "${state}" }) }).then(res => res.redirect(res.url)); } </script> <button onclick="login()">登录</button> </body> </html> `); }); // 2. 登录回调:验证Firebase令牌,生成OIDC授权码并重定向到Budibase app.post('/callback', async (req, res) => { try { const { firebaseIdToken, state } = req.body; // 验证Firebase ID Token的合法性 const decodedFirebaseToken = await admin.auth().verifyIdToken(firebaseIdToken); // 生成临时授权码(生产环境建议存在Redis/内存中,带过期时间) const authCode = Math.random().toString(36).substring(2, 15); // 重定向回Budibase的回调地址,携带授权码和state const redirectUrl = new URL(CONFIG.BUDIBASE_REDIRECT_URI); redirectUrl.searchParams.set('code', authCode); redirectUrl.searchParams.set('state', state); res.redirect(redirectUrl.toString()); } catch (err) { res.status(401).send('Invalid Firebase Token'); } }); // 3. OIDC令牌端点:用授权码换取OIDC ID Token app.post('/token', async (req, res) => { try { const { code, client_id, client_secret } = req.body; // 验证客户端身份 if (client_id !== CONFIG.BUDIBASE_CLIENT_ID || client_secret !== CONFIG.BUDIBASE_CLIENT_SECRET) { return res.status(401).json({ error: 'invalid_client' }); } // 验证授权码(生产环境需校验存储的授权码有效性) // 简化处理,直接生成OIDC ID Token const oidcIdToken = jwt.sign({ sub: decodedFirebaseToken.uid, name: decodedFirebaseToken.name, email: decodedFirebaseToken.email, email_verified: decodedFirebaseToken.email_verified, iss: 'https://your-proxy-domain.com', // 你的代理域名 aud: CONFIG.BUDIBASE_CLIENT_ID, exp: Math.floor(Date.now() / 1000) + 3600, // 1小时过期 iat: Math.floor(Date.now() / 1000) }, CONFIG.JWT_SIGNING_KEY, { algorithm: 'RS256' }); res.json({ id_token: oidcIdToken, token_type: 'Bearer', expires_in: 3600 }); } catch (err) { res.status(500).json({ error: err.message }); } }); // 4. OIDC用户信息端点(Budibase可能需要) app.get('/userinfo', async (req, res) => { try { const authHeader = req.headers.authorization; if (!authHeader) return res.status(401).json({ error: 'unauthorized' }); const token = authHeader.split(' ')[1]; const decodedOidcToken = jwt.verify(token, CONFIG.JWT_SIGNING_KEY); const firebaseUser = await admin.auth().getUser(decodedOidcToken.sub); res.json({ sub: firebaseUser.uid, name: firebaseUser.displayName, email: firebaseUser.email, email_verified: firebaseUser.emailVerified }); } catch (err) { res.status(401).json({ error: 'invalid_token' }); } }); app.listen(3000, () => console.log('OIDC Proxy running on port 3000'));
优势
- 仅保留必要功能,代码量少,易于维护
- 所有敏感配置通过环境变量管理,避免硬编码
- 安全层面:验证Firebase令牌合法性,用非对称密钥签名OIDC令牌,防止篡改
方案2:无服务器代理(Firebase Cloud Functions/Vercel Edge Functions)
如果不想维护服务器,可以把上述代理逻辑部署为无服务器函数,比如Firebase Cloud Functions:
- 将代码适配为云函数的格式
- 环境变量配置在云函数的控制台中
- 优势:自动扩缩容,无需服务器运维成本
方案3:直接修改Budibase登录流程(跳过OIDC中间层)
如果你的技术团队能修改Budibase的前端代码,这是最直接的方案:
- 在Budibase的登录页面嵌入Firebase Web SDK,实现Firebase登录
- 将用户的Firebase ID Token存储到Budibase的用户会话元数据中
- 配置Budibase的API集成时,从用户元数据中读取令牌,自动添加到
Authorization头
优势
- 无需搭建额外的OIDC代理层
- 流程更短,减少潜在故障点
安全注意事项
- 令牌验证:必须用Firebase Admin SDK验证Firebase ID Token的合法性,防止伪造
- 密钥管理:OIDC令牌的签名密钥要用RSA非对称密钥,私钥妥善保管,公钥暴露给Budibase用于验证令牌
- CSRF防护:授权流程中必须携带
state参数,防止跨站请求伪造 - 令牌过期:OIDC令牌的过期时间设置为1小时以内,避免长期有效
- 客户端校验:仅允许预配置的Budibase客户端接入,拒绝未知客户端的请求
内容的提问来源于stack exchange,提问作者Hannes Hultergård
相关产品推荐
相关产品推荐

