手动验证 Firebase 身份验证令牌

问题描述 投票:0回答:3

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

我使用 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/[email protected]')).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];
}
javascript firebase firebase-authentication jwt cloudflare-workers
3个回答
7
投票

请注意,您可以从端点获取 jwks

https://www.googleapis.com/service_accounts/v1/jwk/[email protected]
(记录于此处)。

async function fetchPublicKey(kid) {
  const result = await (
    await fetch(
      "https://www.googleapis.com/service_accounts/v1/jwk/[email protected]"
    )
  ).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);

2
投票

您的代码存在一些问题:

  1. 您调用获取公钥的 URL 返回 x509 证书列表。这些不是用于验证签名的公钥。您确定您无法直接访问公钥吗?似乎可以从 x509 证书获取公钥信息(如下所述:从 X.509 证书中提取 PEM 公钥),尽管我不确定这是否可以从 Cloudflare 工作人员那里获得。

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

  3. 您正在使用

    sign
    方法来验证签名。事情不是这样的。您应该使用
    verify
    crypto.subtle
    方法,此方法将为您验证签名。

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


2
投票

您可以使用 @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>");
© www.soinside.com 2019 - 2024. All rights reserved.