You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase Auth自定义JWT篡改风险及权限验证最佳实践咨询

Firebase Auth自定义声明篡改的安全问题与解决方案

问题背景

我基于Firebase v2版onCall云函数开发无服务器后端,编写了中间件实现认证与基于角色的访问控制(RBAC),部分API要求用户令牌包含roles: ["admin", "superadmin"]自定义声明。但测试发现,用户可篡改JWT令牌中的roles属性,从而绕过权限控制获取管理员权限。

复现步骤

  • 用户通过邮箱密码登录Firebase Auth,获取包含roles: ["basic"]的Bearer令牌
  • 解码令牌,将roles改为["admin"]后重新编码
  • 在Postman中使用篡改后的令牌调用API,成功获得管理员权限

现有中间件代码

export const withAuthentication =
  (
    requiredAccessRoles?: string[] // Roles are optional
  ) =>
  (
    handler: (request: CallableRequest) => Promise<any> // Function to handle the request
  ): ((request: CallableRequest) => Promise<any>) => {
    return async (request) => {
      const authHeader = request.rawRequest.headers.authorization as
        | string
        | undefined;

      // Check if Authorization header is present and properly formatted
      if (!authHeader || !authHeader.startsWith("Bearer "))
        throw new HttpsError("unauthenticated", "Invalid or missing ID token.");

      if (!request.auth || !request.auth.uid)
        throw new HttpsError("unauthenticated", "Unauthenticated user");

      // Extract the ID token from the header
      const idToken = authHeader.slice(7).trim();

      try {
        // Validate the ID token
        await getAuth().verifyIdToken(idToken);
      } catch (error) {
        throw new HttpsError("unauthenticated", "Invalid or expired ID token.");
      }

      // If roles are required, perform the role check
      if (requiredAccessRoles && requiredAccessRoles.length > 0) {
        // Check if the user has at least one of the required roles
        const hasAnyRole = requiredAccessRoles.some((role) =>
          request.auth?.token.roles.includes(role)
        );

        if (!hasAnyRole)
          throw new HttpsError("permission-denied", "Insufficient privileges");
      }

      // Proceed with the original handler
      return handler(request);
    };
  };

云函数示例代码

export const testApiEndpoint = onCall(
  withAuthentication(["admin", "superadmin"])(async (request) => {
    try {
      return {
        message: "access granted"
      };
    } catch (error) {
      throw new HttpsError("internal", "Something went wrong");
    }
  })
);

问题解答

1. 如何防范篡改令牌提权的安全漏洞?

你的核心问题是错误依赖了未经过服务端可信验证的request.auth.token.roles,同时中间件逻辑存在冗余验证导致的逻辑漏洞:

  • 对于Firebase onCall云函数,平台会自动验证请求中的ID令牌,仅当令牌有效时才会填充request.auth,因此request.auth本身是可信的。但你手动提取令牌并验证后,仍使用request.auth.token(可能来自客户端篡改后的令牌)来检查角色,这是风险点。
  • 若你选择手动验证令牌,必须使用verifyIdToken返回的经过签名验证的解码令牌来获取自定义声明,而非客户端传递的原始数据。

修改后的中间件代码:

export const withAuthentication =
  (requiredAccessRoles?: string[]) =>
  (handler: (request: CallableRequest) => Promise<any>): ((request: CallableRequest) => Promise<any>) => {
    return async (request) => {
      // 依赖Firebase onCall自动验证的可信auth数据
      if (!request.auth || !request.auth.uid) {
        throw new HttpsError("unauthenticated", "Unauthenticated user");
      }

      // 角色校验使用可信的request.auth.token(由Firebase验证后填充)
      if (requiredAccessRoles && requiredAccessRoles.length > 0) {
        const userRoles = request.auth.token.roles as string[] || [];
        const hasAnyRole = requiredAccessRoles.some(role => userRoles.includes(role));
        
        if (!hasAnyRole) {
          throw new HttpsError("permission-denied", "Insufficient privileges");
        }
      }

      return handler(request);
    };
  };

若你需要手动验证令牌(比如非onCall场景),则必须使用验证后的解码令牌:

// 手动验证场景下的角色校验逻辑
const decodedToken = await getAuth().verifyIdToken(idToken);
const userRoles = decodedToken.roles as string[] || [];
const hasAnyRole = requiredAccessRoles.some(role => userRoles.includes(role));

2. Firebase Auth中安全验证自定义声明角色的最佳实践

  • 依赖Firebase原生验证机制:对于onCall/onRequest云函数,优先使用平台自动填充的request.auth数据,避免手动提取令牌重复验证,减少逻辑漏洞。
  • 始终使用服务端验证后的声明:若手动验证令牌,必须从verifyIdToken返回的解码对象中获取自定义声明,绝对不能使用客户端传递的未验证数据。
  • 最小权限原则:仅为用户分配必要的角色,避免过度授权;敏感操作(如修改角色)仅允许超级管理员通过安全的服务端逻辑执行,禁止客户端直接修改自定义声明。
  • 定期刷新令牌:客户端使用getIdToken(true)强制刷新令牌,确保自定义声明变更后能及时同步到客户端,避免用户使用过期的旧令牌。
  • 日志与监控:记录角色校验失败的请求,监控异常的权限访问尝试,及时发现潜在的攻击行为。

内容的提问来源于stack exchange,提问作者Fabio Moggi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 01:54:56