如何将OpenID Connect声明纳入Firebase登录属性?
我们使用谷歌身份平台作为身份提供商,目前支持Email/Password、SAML(SSO)两种认证方式,仅用于身份验证。用户认证后,后端验证ID Token有效性,再用自有密钥集签发JWT,并附加租户ID这类自定义客户端ID数据。
现在计划新增OpenID Connect认证方式(对接Azure AD,即Microsoft Entra),认证流程已可运行,但需要将Azure AD返回的given_name、family_name字段纳入现有流程。当前前端流程代码如下:
// 已预配置Scope为"openid profile email" const provider = new OAuthProvider(ssoId); await signInWithRedirect($auth, provider);
用户认证后有两种处理选择:
- 获取Firebase ID Token发送至后端,但该Token缺少邮箱、姓名等信息;
- 提取微软ID Token,其中包含
given_name和family_name:
const userCredential = await getRedirectResult($auth); if (userCredential) { const credentials = OAuthProvider.credentialFromResult(userCredential); console.log(credentials); // 微软返回的Access Token + ID Token console.log(userCredential); // 解码后的Firebase ID Token }
我们希望沿用SAML认证的流程,将这些字段映射到Firebase的sign_in_attributes中,但当前Firebase ID Token的sign_in_attributes里缺少所需信息,示例如下:
{ "name": "*redacted*", "iss": "https://securetoken.google.com/*redacted*-dev", "aud": "*redacted*-dev", "auth_time": 1695129924, "user_id": "zLqx1u73QaU9uOmkaNvEZZbkNh42", "sub": "zLqx1u73QaU9uOmkaNvEZZbkNh42", "iat": 1695129924, "exp": 1695133524, "firebase": { "identities": { "oidc.*redacted*-azure-ad": [ "VYmuzDUMQUlSkGnNLHVDQ-vxdvV9qJDhTEMJamqqzos" ] }, "sign_in_provider": "oidc.*redacted*-azure-ad", "sign_in_attributes": { "ver": "2.0", "aio": "AWQAm/8UAAAApsgZB1l8/pUKK3DdCTzioVEHpJOT41hs7XiATk37ShUwjgYCa0HZIOJmOi2GgswENS+u6Sb3y7DVwqR2Na+6JBgiufC24Hn8VMJnP9o2u7fk8/xigzNRjs8S8Qydc4R9", "rh": "0.ATkAcY6U2Y_y7kWFt7vUytrJub9-rLRmV_5MvduzlkIsCrzgAJs.", "oid": "158e8a64-5d48-4641-bf76-7630a0aa79cd", "preferred_username": "*redacted*@*redacted*.onmicrosoft.com", "uti": "PsyrhHx2bk294jOC7FgPAA", "tid": "d9948e71-f28f-45ee-85b7-bbd4cadac9b9" }, "tenant": "*redacted*-test-*redacted*-gnh1z" } }
现咨询:如何将OpenID Connect ID Token中的指定声明纳入Firebase的sign_in_attributes?若无法实现,推荐的替代流程是什么?
一、尝试将OIDC声明映射到Firebase sign_in_attributes
Firebase对OIDC提供商的sign_in_attributes填充逻辑是固定的,无法像SAML那样自定义映射规则,但可以尝试以下两种方式:
强制Azure AD输出指定声明
在Azure AD应用注册的「令牌配置」中,添加given_name、family_name作为可选声明,强制要求这些字段包含在ID Token中。虽然已请求profilescope,但Azure AD默认可能不会将所有profile字段写入ID Token,手动配置可选声明有可能让Firebase在处理时同步这些字段到sign_in_attributes(此方法不保证100%生效,取决于Firebase的OIDC属性提取规则)。通过Firebase云函数写入自定义Claims
触发用户登录/创建的云函数(onSignIn或onCreate),从微软ID Token中解析出given_name和family_name,调用Firebase Admin SDK的updateUser方法将这些字段写入用户的自定义Claims。自定义Claims会出现在Firebase ID Token的顶层(而非sign_in_attributes内),但后端可直接读取,能达到类似SAML流程的效果。
二、替代流程推荐
如果上述方法无法实现,推荐以下两种兼容现有后端流程的方案:
方案1:前端同时发送Firebase ID Token和微软ID Token
前端获取到userCredential后,同时提取Firebase ID Token和微软ID Token,一起发送给后端。后端先验证Firebase ID Token的有效性,再解码微软ID Token提取given_name、family_name,最后签发自有JWT时附加这些数据。
优点:无需改动Firebase配置,逻辑直接;
缺点:前端多传一个Token,后端需额外验证微软ID Token的签名(可使用Azure AD公钥验证)。
前端代码示例:
const userCredential = await getRedirectResult($auth); if (userCredential) { const credentials = OAuthProvider.credentialFromResult(userCredential); const firebaseIdToken = await userCredential.user.getIdToken(); // 同时发送两个Token到后端 await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ firebaseIdToken, microsoftIdToken: credentials.idToken }) }); }
方案2:后端通过Firebase Admin SDK+Azure AD Graph API补充字段
前端仅发送Firebase ID Token到后端,后端验证后通过Firebase Admin SDK获取用户的provider数据,若其中不包含given_name和family_name,再调用Azure AD Graph API,根据用户的OIDC身份ID获取完整profile信息。
优点:前端改动小,后端统一处理;
缺点:需后端集成Azure AD Graph API,增加依赖。
后端代码示例(Node.js):
const admin = require('firebase-admin'); admin.initializeApp(); async function handleLogin(req, res) { const { firebaseIdToken } = req.body; // 验证Firebase ID Token const decodedToken = await admin.auth().verifyIdToken(firebaseIdToken); // 获取用户的provider数据 const user = await admin.auth().getUser(decodedToken.uid); const microsoftProvider = user.providerData.find(p => p.providerId === 'oidc.your-azure-ad-provider-id'); let givenName, familyName; if (microsoftProvider) { // 先从providerData中提取,没有则调用Graph API givenName = microsoftProvider.givenName; familyName = microsoftProvider.familyName; if (!givenName || !familyName) { // 调用Azure AD Graph API获取用户信息 const graphResponse = await fetch(`https://graph.microsoft.com/v1.0/users/${microsoftProvider.uid}`, { headers: { 'Authorization': `Bearer ${yourAzureADAccessToken}` } }); const graphUser = await graphResponse.json(); givenName = graphUser.givenName; familyName = graphUser.familyName; } } // 签发自有JWT并附加字段 const customJwt = require('jsonwebtoken').sign({ userId: decodedToken.uid, tenantId: decodedToken.firebase.tenant, given_name: givenName, family_name: familyName }, process.env.JWT_SECRET, { expiresIn: '1h' }); res.json({ token: customJwt }); }
内容的提问来源于stack exchange,提问作者nook

