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.
none
foralg
), or encrypted. Therefore there is no verifying it bcs there's no signature to examine. – ArrestRS256
. It should be verifiable according to Firebase docs. Also, hello from a fellow New Orleanian. – Pyrogen