firebase-edge-auth
Version:
Firebase token decoder for edge runtimes
79 lines (67 loc) • 1.86 kB
text/typescript
import { jwtVerify, importX509, JWTPayload, JWSHeaderParameters } from "jose";
const FIREBASE_PUBLIC_KEYS_URL =
"https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
let publicKeys: Record<string, string> = {};
let lastFetchTime = 0;
export async function fetchPublicKeys(): Promise<void> {
const response = await fetch(FIREBASE_PUBLIC_KEYS_URL);
if (!response.ok) {
throw new Error("Failed to fetch Firebase public keys");
}
publicKeys = await response.json();
lastFetchTime = Date.now();
}
async function getPublicKey(kid: string): Promise<CryptoKey> {
if (
Date.now() - lastFetchTime > 3600000 ||
Object.keys(publicKeys).length === 0
) {
await fetchPublicKeys();
}
if (!(kid in publicKeys)) {
throw new Error("Public key not found");
}
return await importX509(publicKeys[kid], "RS256");
}
interface FirebaseIdentities {
[key: string]: string[];
}
interface FirebaseInfo {
identities: FirebaseIdentities;
sign_in_provider: string;
}
interface FirebasePayload extends JWTPayload {
iss: string;
aud: string;
auth_time: number;
user_id: string;
sub: string;
iat: number;
exp: number;
email: string;
email_verified: boolean;
firebase: FirebaseInfo;
}
export async function decodeFirebaseToken(
token: string,
projectId: string
): Promise<FirebasePayload> {
try {
const { payload } = await jwtVerify(
token,
async (header: JWSHeaderParameters) => {
if (!header.kid) {
throw new Error("No 'kid' claim in token header");
}
return getPublicKey(header.kid);
},
{
audience: projectId,
issuer: `https://securetoken.google.com/${projectId}`,
}
);
return payload as FirebasePayload;
} catch (error) {
throw new Error("Invalid Firebase token");
}
}