I’m trying to verify a JWT (session cookie) following the instructions here guided by this sample implementation in Python using the jose package (although I’m open to other node packages).
Why?
I’m aware that I can use Firebase’s verifySessionCookie
to do this. In fact, that’s what I’m doing currently and it works..
export async function getDecodedSessionCookie() {
// Get the sessionCookie
const sessionCookie = cookies().get("sessionCookie")
if (sessionCookie === undefined) return null
// Verify the cookie but don't check if the cookie has
// been revoked not sure if this is a security risk,
// but it appears to add significant latency
return (
adminAuth
.verifySessionCookie(sessionCookie.value, false)
// If the cookie is verified, return the decodedClaims
.then((decodedClaims) => {
return decodedClaims
})
.catch((e) => console.log("error", e))
)
}
BUT it’s annoyingly slow and it can’t be executed in Vercel’s Edge runtime.
What I’ve Tried
This topic is a little above my head, but here’s what I’ve tried..
export async function getDecodedSessionCookie2() {
// Return null if the cookie doesn't exist or it's invalid
const sessionCookie = cookies().get("sessionCookie")
if (sessionCookie === undefined) return null
// Decode the header (this works)
const header = jose.decodeProtectedHeader(sessionCookie.value)
console.log("header", header)
// Decode the cookie (this works)
const sessionCookieDecoded = jose.decodeJwt(sessionCookie.value)
console.log("sessionCookieDecoded", sessionCookieDecoded)
// Create the remote key set
// (This errors with message: JSON Web Key Set malformed)
const JWKS = jose.createRemoteJWKSet(
new URL(
"https://www.googleapis.com/robot/v1/metadata/x509/[email protected]"
)
)
const keyset = await JWKS()
console.log("keyset", keyset)
// Never made it here
const audience = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID
const issuer = `https://securetoken.google.com/${audience}`
// Never made it here
const { payload, protectedHeader } = await jose.jwtVerify(
sessionCookie.value,
JWKS,
{
issuer,
audience,
}
)
console.log("protectedHeader", protectedHeader)
console.log("payload", payload)
// Not sure if this is needed?
// const x509 = certificates["7cf7f8727091e4c77aa995db60743b7dd2bb70b5"]
// const ecPublicKey = await jose.importX509(x509, algorithm)
return sessionCookieDecoded
}
Mostly this is just a lot of tinkering and exploration, but I think I need to create a remote keyset with createRemoteJWKSet
and this is the step I can’t get past.
Additional notes
- Firebase tokens should have a
kid
in the header.- In production, I see
kid: lk02Aw
. As far as I can tell, this does not correspond to any of the public keys - In local development with the Auth Emulator,
kid
does not exist.
- In production, I see
- Do the public certificates change frequently?
Updates
-
I was able to get past the error above with some guidance from the author of the jose package. Will update with complete details if/when I finish implementing token verification.
-
I found this post by John Hanley noting that Google’s public keys rotate every 12 hours.
2
Answers
I was able to work this out with a lot of help from @panva (the author of jose). So, shout out to him!
Code
Here’s how to do it with native Node.
Get Token
I was able to get my token from the browser by opening Developer Tools (cntrl+shift+i) and entering:
Get Public Key(s)
Google’s Public Keys are published at https://www.googleapis.com/robot/v1/metadata/x509/[email protected] passed as a string
publicKeyGoogle
to script below.Run Node Script