手动验证 Firebase Auth 令牌
Validating Firebase Auth tokens manually
我正在尝试使用 cloudflare workers 来执行经过身份验证的操作。
我正在使用 firebase 进行身份验证,并且可以访问通过的访问令牌,但是由于 firebase-admin 使用 nodejs 模块,它无法在平台上工作,所以我只能手动验证令牌。
我一直在尝试使用 Crypto API 进行身份验证,最后让它导入 public 密钥对令牌进行签名以检查其是否有效,但我总是得到 FALSE。我正在努力弄清楚为什么它总是返回错误的有效性。
我导入的加密密钥以“secret”类型出现,我希望它是“public”。
任何想法或帮助都是巨大的。在过去的几天里,我一直在苦苦思索 table 试图解决这个问题
这是我目前拥有的:
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];
}
您的代码存在一些问题:
您调用 URL 获取 public 密钥 returns x509 证书列表。这些不是用于验证签名的 public 密钥。您确定您不能直接访问 public 键吗?似乎可以从 x509 证书中获取 public 密钥信息(如此处所述:Extract PEM Public Key from X.509 Certificate),但我不确定 Cloudflare 工作人员是否可以这样做。
在 importPublicKey
中,您告诉 import
方法,该密钥是原始格式并且它是一个 HMAC
密钥。这意味着加密将您的密钥视为对称 HMAC 密钥,而不是 public 密钥。根据文档:https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#subjectpublickeyinfo 您应该使用 spki
格式,因为这是导入 public 密钥的格式。您必须事先知道 JWT 访问令牌是使用 RSA 还是椭圆曲线算法签名的。 (例如检查 alg
header 声明)
您正在使用 sign
方法来验证签名。它不是这样工作的。您应该使用 crypto.subtle
的 verify
方法,此方法将为您验证签名。
我认为您不应该尝试手动验证 JWT,因为您很可能会做错(并为您的应用程序带来安全问题)。您应该使用处理 JWT 签名验证的库。这对您来说会更容易,对您的应用程序来说也会更安全。您必须弄清楚的一件事是您应该从哪里获取 public 密钥。
请注意,您可以从未记录的端点获取 jwks https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com。
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);
我正在尝试使用 cloudflare workers 来执行经过身份验证的操作。
我正在使用 firebase 进行身份验证,并且可以访问通过的访问令牌,但是由于 firebase-admin 使用 nodejs 模块,它无法在平台上工作,所以我只能手动验证令牌。
我一直在尝试使用 Crypto API 进行身份验证,最后让它导入 public 密钥对令牌进行签名以检查其是否有效,但我总是得到 FALSE。我正在努力弄清楚为什么它总是返回错误的有效性。
我导入的加密密钥以“secret”类型出现,我希望它是“public”。
任何想法或帮助都是巨大的。在过去的几天里,我一直在苦苦思索 table 试图解决这个问题
这是我目前拥有的:
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];
}
您的代码存在一些问题:
您调用 URL 获取 public 密钥 returns x509 证书列表。这些不是用于验证签名的 public 密钥。您确定您不能直接访问 public 键吗?似乎可以从 x509 证书中获取 public 密钥信息(如此处所述:Extract PEM Public Key from X.509 Certificate),但我不确定 Cloudflare 工作人员是否可以这样做。
在
importPublicKey
中,您告诉import
方法,该密钥是原始格式并且它是一个HMAC
密钥。这意味着加密将您的密钥视为对称 HMAC 密钥,而不是 public 密钥。根据文档:https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#subjectpublickeyinfo 您应该使用spki
格式,因为这是导入 public 密钥的格式。您必须事先知道 JWT 访问令牌是使用 RSA 还是椭圆曲线算法签名的。 (例如检查alg
header 声明)您正在使用
sign
方法来验证签名。它不是这样工作的。您应该使用crypto.subtle
的verify
方法,此方法将为您验证签名。
我认为您不应该尝试手动验证 JWT,因为您很可能会做错(并为您的应用程序带来安全问题)。您应该使用处理 JWT 签名验证的库。这对您来说会更容易,对您的应用程序来说也会更安全。您必须弄清楚的一件事是您应该从哪里获取 public 密钥。
请注意,您可以从未记录的端点获取 jwks https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com。
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);