【问题标题】:Validating Firebase Auth tokens manually手动验证 Firebase 身份验证令牌
【发布时间】:2022-07-16 18:46:02
【问题描述】:

我正在尝试使用 cloudflare worker 来执行经过身份验证的操作。

我正在使用 firebase 进行身份验证,并且可以访问通过的访问令牌,但是由于 firebase-admin 使用 nodejs 模块,它无法在平台上工作,所以我只能手动验证令牌。

我一直在尝试使用 Crypto API 进行身份验证,最后让它导入公钥签名令牌以检查其是否有效,但我一直收到 FALSE。我正在努力弄清楚为什么它总是返回 false 的有效性。

我导入的加密密钥以“秘密”类型输入,我希望它是“公开的”。

任何想法或帮助都会是巨大的。在过去的几天里,我一直在用头撞桌子,试图弄清楚这一点

这是我目前所拥有的:

function _utf8ToUint8Array(str) {
    return Base64URL.parse(btoa(unescape(encodeURIComponent(str))))
}

class Base64URL {
    static parse(s) {
        return new Uint8Array(Array.prototype.map.call(atob(s.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '')), c => c.charCodeAt(0)))
    }
    static stringify(a) {
        return btoa(String.fromCharCode.apply(0, a)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
    }
}


export async function verify(userToken: string) {
    let jwt = decodeJWT(userToken)
    var jwKey = await fetchPublicKey(jwt.header.kid);
    let publicKey = await importPublicKey(jwKey);
    var isValid = await verifyPublicKey(publicKey, userToken);
    console.log('isValid', isValid) // RETURNS FALSE
    return isValid;
}

function decodeJWT(jwtString: string): IJWT {
    // @ts-ignore
    const jwt: IJWT = jwtString.match(
        /(?<header>[^.]+)\.(?<payload>[^.]+)\.(?<signature>[^.]+)/
    ).groups;

    // @ts-ignore
    jwt.header = JSON.parse(atob(jwt.header));
    // @ts-ignore
    jwt.payload = JSON.parse(atob(jwt.payload));

    return jwt;
}

async function fetchPublicKey(kid: string) {
    var key: any = await (await fetch('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com')).json();

    key = key[kid];
    key = _utf8ToUint8Array(key)
    return key;
}

function importPublicKey(jwKey) {
    return crypto.subtle.importKey('raw', jwKey, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']);
}

async function verifyPublicKey(publicKey: CryptoKey, token: string) {
    const tokenParts = token.split('.')
    let res = await crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-256' } }, publicKey, _utf8ToUint8Array(tokenParts.slice(0, 2).join('.')))
    return Base64URL.stringify(new Uint8Array(res)) === tokenParts[2];
}

【问题讨论】:

  • Cloudflare 工作人员确实支持 Cloudflare worker JWT 软件包。你试过用那个吗?
  • 是的,不幸的是没有运气

标签: javascript firebase firebase-authentication jwt cloudflare-workers


【解决方案1】:

您的代码存在一些问题:

  1. 您为获取公钥而调用的 URL 返回 x509 证书列表。这些不是用于验证签名的公钥。您确定您没有直接访问公钥的权限吗?似乎可以从 x509 证书中获取公钥信息(如此处所述:Extract PEM Public Key from X.509 Certificate),但我不确定 Cloudflare 工作人员是否可以这样做。

  2. importPublicKey 中,您告诉import 方法,密钥是原始格式并且是HMAC 密钥。这意味着加密将您的密钥视为对称 HMAC 密钥,而不是公钥。根据文档:https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#subjectpublickeyinfo 您应该使用spki 格式,因为这是导入公钥的格式。您必须预先知道 JWT 访问令牌是使用 RSA 还是椭圆曲线算法签名的。 (例如,检查 alg 标头声明)

  3. 您正在使用sign 方法来验证签名。这不是它的工作原理。您应该使用crypto.subtleverify 方法,此方法将为您验证签名。

我认为您不应该尝试手动验证 JWT,因为您很可能会做错(并为您的应用程序带来安全问题)。您应该使用处理 JWT 签名验证的库。这对您来说会更容易,对您的应用程序来说也更安全。您必须弄清楚的一件事是您应该从哪里获取公钥。

【讨论】:

  • 嗨,Michal,谢谢你的想法。我将深入研究 1 并修复 2 和 3。同意手动验证 JWT,我希望加密库会有所帮助。我最大的问题是我真的找不到不使用节点进行验证的 JWT 库。我通过这个文档偶然发现了 1:firebase.google.com/docs/auth/admin/…
  • 能够让它与 jsrsasign 一起工作,谢谢您的帮助!。我搬到了验证。 X.509 证书似乎有效
【解决方案2】:

请注意,您可以从未记录的端点 https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com 获取 jwks。

async function fetchPublicKey(kid) {
  const result = await (
    await fetch(
      "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com"
    )
  ).json();

  return result.keys.find((key) => key.kid === kid);
}

使用密钥(下面命名为jwk),可以验证签名:

  const encoder = new TextEncoder();
  const data = encoder.encode([token.raw.header, token.raw.payload].join("."));
  const signature = new Uint8Array(
    Array.from(token.signature).map((c) => c.charCodeAt(0))
  );
  const key = await crypto.subtle.importKey(
    "jwk",
    jwk,
    { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
    false,
    ["verify"]
  );

  return crypto.subtle.verify("RSASSA-PKCS1-v1_5", key, signature, data);

【讨论】:

    【解决方案3】:

    您可以使用 @codehelios/verify-tokenid 库来验证 Cloudflare Workers 上的 Firebase ID 令牌。

    例子:

    import { verifyTokenId } from "@codehelios/verify-tokenid";
    
    const tokenId = "<ID_TOKEN>"
    
    const { isValid, decoded, error } = await verifyTokenId(tokenId, "https://securetoken.google.com/<projectId>", "<projectId>");
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-28
      • 2016-09-29
      • 2021-09-23
      • 1970-01-01
      • 2017-12-15
      • 1970-01-01
      相关资源
      最近更新 更多