Created
December 2, 2021 14:25
-
-
Save a88zach/2970966fe37422b968028cd8158e0724 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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