From 3db44824a5df63c5189a6db1753f9be6816d42fa Mon Sep 17 00:00:00 2001 From: Dane Grant Date: Sat, 20 Aug 2022 13:44:12 -0400 Subject: [PATCH 1/2] feat: adds support for firebase auth emulator --- src/{utils/fetchPublicKeys.ts => utils.ts} | 15 +++++++++ src/utils/headers.ts | 8 ----- src/utils/jwt.ts | 5 --- src/withFirebaseUser.ts | 38 +++++++++++++--------- 4 files changed, 37 insertions(+), 29 deletions(-) rename src/{utils/fetchPublicKeys.ts => utils.ts} (60%) delete mode 100644 src/utils/headers.ts delete mode 100644 src/utils/jwt.ts diff --git a/src/utils/fetchPublicKeys.ts b/src/utils.ts similarity index 60% rename from src/utils/fetchPublicKeys.ts rename to src/utils.ts index d69c09c..76be2a2 100644 --- a/src/utils/fetchPublicKeys.ts +++ b/src/utils.ts @@ -21,3 +21,18 @@ export function fetchPublicKeys( }); }); } + +export function decodeJwtHeader(jwt: string) { + const [base64] = jwt.split('.'); + const buffer = Buffer.from(base64, 'base64'); + return buffer.toString(); +} + +export function getBearerToken(auth?: string) { + if (auth) { + const [scheme, credentials] = auth.split(' '); + if (scheme.toLocaleLowerCase() === 'bearer') { + return credentials; + } + } +} diff --git a/src/utils/headers.ts b/src/utils/headers.ts deleted file mode 100644 index 7222a9f..0000000 --- a/src/utils/headers.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function getBearerToken(auth?: string) { - if (auth) { - const [scheme, credentials] = auth.split(' '); - if (scheme.toLocaleLowerCase() === 'bearer') { - return credentials; - } - } -} diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts deleted file mode 100644 index c418f66..0000000 --- a/src/utils/jwt.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function decodeJwtHeader(jwt: string) { - const [base64] = jwt.split('.'); - const buffer = Buffer.from(base64, 'base64'); - return buffer.toString(); -} diff --git a/src/withFirebaseUser.ts b/src/withFirebaseUser.ts index 389c402..b4266d7 100644 --- a/src/withFirebaseUser.ts +++ b/src/withFirebaseUser.ts @@ -1,17 +1,17 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequestWithFirebaseUser, FirebaseUser } from './types'; -import jwt from 'jsonwebtoken'; -import { getBearerToken } from './utils/headers'; -import { decodeJwtHeader } from './utils/jwt'; -import { fetchPublicKeys } from './utils/fetchPublicKeys'; +import jwt, { JwtPayload } from 'jsonwebtoken'; +import { getBearerToken, decodeJwtHeader, fetchPublicKeys } from './utils'; -const FIREBASE_AUTH_CERT_URL = - 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'; interface WithFirebaseUserOptions { clientCertUrl?: string; projectId?: string; + isEmulator: boolean; } +const FIREBASE_AUTH_CERT_URL = + 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'; + export const withFirebaseUser = ( handler: ( @@ -22,15 +22,16 @@ export const withFirebaseUser = ) => async (req: NextApiRequest, res: NextApiResponse) => { // merge options with defaults - const { projectId, clientCertUrl } = Object.assign( + const { projectId, clientCertUrl, isEmulator } = Object.assign( {}, { clientCertUrl: FIREBASE_AUTH_CERT_URL, + isEmulator: false, }, options ); - // copy request object for decoration with firebase user + // copy request object const decoratedReq: NextApiRequestWithFirebaseUser = Object.assign( {}, req, @@ -53,14 +54,19 @@ export const withFirebaseUser = const publicKey = publicKeys[jwtHeader.kid]; if (publicKey) { - // decode jwt with public key - const decodedToken = jwt.verify(accessToken, publicKey, { - audience: projectId, - issuer: projectId && `https://securetoken.google.com/${projectId}`, - }); + let decodedToken: string | JwtPayload | null; + + if (isEmulator) { + decodedToken = jwt.decode(accessToken); + } else { + decodedToken = jwt.verify(accessToken, publicKey, { + audience: projectId, + issuer: projectId && `https://securetoken.google.com/${projectId}`, + }); + } - if (typeof decodedToken === 'object') { - // create user object we decorate req with from decoded token + if (typeof decodedToken === 'object' && decodedToken !== null) { + // might want to support custom claims in the future const user: FirebaseUser = { user_id: decodedToken.user_id ?? decodedToken.sub, name: decodedToken.name, @@ -72,7 +78,7 @@ export const withFirebaseUser = } } } catch (err) { - console.info('withFirebaseUser: ', err); + console.error('withFirebaseUser: ', err); } return handler(decoratedReq, res); From 41be2ce28a0c2af0925c009976dfbd439b46dc3e Mon Sep 17 00:00:00 2001 From: Dane Grant Date: Mon, 22 Aug 2022 08:23:12 -0400 Subject: [PATCH 2/2] docs: adds new option to README --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 4e0807d..2a7cdeb 100644 --- a/README.md +++ b/README.md @@ -81,5 +81,14 @@ const withFirebaseUser: ( interface WithFirebaseUserOptions { clientCertUrl?: string; // defaults to url provided in Firebase auth docs projectId?: string; // verifies the audience and issuer of the JWT when provided + isEmulator?: boolean; // defaults to false } ``` + +## Contributing + +Feel free + +## License + +Do whatever you want with this code