Skip to content

Instantly share code, notes, and snippets.

@a88zach
Created December 2, 2021 14:25
Show Gist options
  • Save a88zach/2970966fe37422b968028cd8158e0724 to your computer and use it in GitHub Desktop.
Save a88zach/2970966fe37422b968028cd8158e0724 to your computer and use it in GitHub Desktop.
import { decode, verify, JwtPayload } from 'jsonwebtoken';
import fetch from 'node-fetch';
const publicUrl =
'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com';
// cache public keys unitl expiration
const publicKeys = new Map<string, string>();
let expiresAt: number;
const getPublicKey = async (kid: string): Promise<string | undefined> => {
if (publicKeys.has(kid) && expiresAt > Date.now()) {
return publicKeys.get(kid);
}
const response = await fetch(publicUrl);
const result = (await response.json()) as Record<string, string>;
const cacheControlHeader = response.headers.get('cache-control') || '';
const matches = cacheControlHeader.match(/max-age=(\d+)/);
const maxAge = matches ? parseInt(matches[1], 10) : -1;
// set next refresh
expiresAt = Date.now() + maxAge * 1000;
// set public keys
publicKeys.clear();
Object.keys(result).forEach((key) => {
publicKeys.set(key, result[key]);
});
return publicKeys.get(kid);
};
export const validate = async (
jwt: string,
firebaseProject: string,
): Promise<string | JwtPayload> => {
const decoded = decode(jwt, { complete: true });
if (!decoded) {
throw new Error('Invalid JWT');
}
const { alg, kid } = decoded.header;
const { exp, iat, aud, iss, sub, auth_time } = decoded.payload;
if (!alg || alg !== 'RS256') {
throw new Error('Invalid algorithm');
}
if (!kid) {
throw new Error('Invalid key identifier');
}
if (!exp || exp < Date.now()) {
throw new Error('JWT has expired');
}
if (!iat || iat > Date.now()) {
throw new Error('JWT was issued for future use');
}
if (!aud || aud !== firebaseProject) {
throw new Error('JWT is not for the current audience');
}
if (!iss || iss !== `https://securetoken.google.com/${firebaseProject}`) {
throw new Error('JWT was not issued by the correct project');
}
if (!sub) {
throw new Error('Subject is invalid');
}
if (!auth_time || auth_time > Date.now()) {
throw new Error('User was authenticated for future use');
}
const key = await getPublicKey(kid);
if (!key) {
throw new Error('No matching public key');
}
return verify(jwt, key);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment