While integrating Firebase Auth into one of my personal projects, I found out that the Firebase Admin SDK requires a Node.js environment, which doesn’t work with the Edge runtime used by Next.js middleware. The solution is to verify ID tokens using a third-party JWT library https://firebase.google.com/docs/auth/admin/verify-id-tokens. I’m using jose (JSON Object Signing and Encryption) module to verify ID tokens.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 import { NextResponse } from 'next/server'; import { importX509, jwtVerify, JWTPayload } from 'jose'; const secretKey = process.env.JWT_SECRET_KEY; const firebaseProjectId = process.env.FIREBASE_PROJECT_ID; if (!secretKey || !firebaseProjectId) { throw new Error( 'Missing JWT_SECRET_KEY or FIREBASE_PROJECT_ID environment variable' ); } let publicKeysCache: Record<string, string> | null = null; const getPublicKeys = async (): Promise<Record<string, string>> => { if (publicKeysCache) { return publicKeysCache; } const res = await fetch( 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com' ); if (!res.ok) { throw new Error('Failed to fetch public keys'); } const publicKeys = await res.json(); publicKeysCache = publicKeys; return publicKeys; }; const verifyFirebaseJwt = async (firebaseJwt: string): Promise<JWTPayload> => { const publicKeys = await getPublicKeys(); const { payload } = await jwtVerify( firebaseJwt, async (header) => { const x509Cert = publicKeys[header.kid!]; if (!x509Cert) { throw new Error('Invalid key ID'); } return await importX509(x509Cert, 'RS256'); }, { issuer: `https://securetoken.google.com/${firebaseProjectId}`, audience: firebaseProjectId, algorithms: ['RS256'], } ); return payload; }; const createJsonResponse = (data: any, status: number): NextResponse => { return new NextResponse(JSON.stringify(data), { status, headers: { 'Content-Type': 'application/json', }, }); }; export async function middleware(req: any): Promise<NextResponse> { const token = req.headers.get('Authorization')?.split('Bearer ')[1]; if (!token) { return createJsonResponse({ error: 'Unauthorized' }, 401); } try { await verifyFirebaseJwt(token); return NextResponse.next(); } catch (error) { console.error('JWT verification failed:', error); return createJsonResponse({ error: 'Unauthorized' }, 401); } } export const config = { matcher: ['/api/:path*'], }; Fetching Public Keys: The getPublicKeys function fetches the public keys from the Google API. These keys are used to verify the JWT tokens.
...