From 5d5d00e9a616f188a2c84f80241643594768d7a7 Mon Sep 17 00:00:00 2001 From: Scott Prue Date: Fri, 10 Dec 2021 13:03:46 -0700 Subject: [PATCH 1/3] Support default credentials for Firebase Admin --- README.md | 12 +++++++++-- src/__tests__/initFirebaseAdminSDK.test.js | 23 +++++++++++++++++++++- src/initFirebaseAdminSDK.js | 15 ++++++++------ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a9b7d6c1..f1d1f1ca 100644 --- a/README.md +++ b/README.md @@ -86,11 +86,13 @@ const initAuth = () => { credential: { projectId: 'my-example-app-id', clientEmail: 'example-abc123@my-example-app.iam.gserviceaccount.com', - // The private key must not be accesssible on the client side. + // The private key must not be accessible on the client side. privateKey: process.env.FIREBASE_PRIVATE_KEY, }, databaseURL: 'https://my-example-app.firebaseio.com', }, + // Use application default credentials (takes precedence over fireaseAdminInitConfig if set) + // useFirebaseAdminDefaultCredential: true, firebaseClientInitConfig: { apiKey: 'MyExampleAppAPIKey123', // required authDomain: 'my-example-app.firebaseapp.com', @@ -440,7 +442,7 @@ Configuration passed to `firebase-admin`'s [`initializeApp`](https://firebase.go The `firebaseAdminInitConfig.credential.privateKey` cannot be defined on the client side and should live in a secret environment variable. -> Note: if using environent variables in Vercel, add the private key _with double quotes_ via the CLI: +> Note: if using environment variables in Vercel, add the private key _with double quotes_ via the CLI: > > `vercel secrets add firebase-private-key '"my-key-here"'` > @@ -454,6 +456,12 @@ The `firebaseAdminInitConfig.credential.privateKey` cannot be defined on the cli > > See [this Vercel issue](https://github.com/vercel/vercel/issues/749#issuecomment-707515089) for more information. +**useFirebaseAdminDefaultCredential** + +A boolean that when set to true, the application default credential will be passed to `firebase-admin`'s [`initializeApp`](https://firebase.google.com/docs/admin/setup#initialize-sdk). + +**NOTE**: When set to true default credentials will override values passed to `firebaseAdminInitConfig.credential` + **firebaseClientInitConfig**: Configuration passed to the Firebase JS SDK's [`initializeApp`](https://firebase.google.com/docs/reference/node/firebase#initializeapp). The "firebaseClientInitConfig.apiKey" value is always **required**. Other properties are required unless you initialize the `firebase` app yourself before initializing `next-firebase-auth`. #### **cookies** diff --git a/src/__tests__/initFirebaseAdminSDK.test.js b/src/__tests__/initFirebaseAdminSDK.test.js index 99d54eb5..a1ab55cd 100644 --- a/src/__tests__/initFirebaseAdminSDK.test.js +++ b/src/__tests__/initFirebaseAdminSDK.test.js @@ -16,6 +16,9 @@ beforeEach(() => { ...obj, _mockFirebaseCert: true, })) + admin.credential.applicationDefault.mockImplementation(() => ({ + _mockFirebaseDefaultCred: true, + })) admin.apps = [] }) @@ -39,6 +42,24 @@ describe('initFirebaseAdminSDK', () => { }) }) + it('calls admin.initializeApp with application default credentials if useFirebaseAdminDefaultCredential set to true', () => { + expect.assertions(2) + const mockConfig = createMockConfig({ clientSide: false }) + setConfig({ + ...mockConfig, + firebaseAdminInitConfig: undefined, + useFirebaseAdminDefaultCredential: true, + }) + const initFirebaseAdminSDK = require('src/initFirebaseAdminSDK').default + initFirebaseAdminSDK() + expect(admin.credential.applicationDefault).toHaveBeenCalled() + expect(admin.initializeApp).toHaveBeenCalledWith({ + credential: { + _mockFirebaseDefaultCred: true, + }, + }) + }) + it('returns the admin app', () => { expect.assertions(1) const initFirebaseAdminSDK = require('src/initFirebaseAdminSDK').default @@ -65,7 +86,7 @@ describe('initFirebaseAdminSDK', () => { expect(() => { initFirebaseAdminSDK() }).toThrow( - 'If not initializing the Firebase admin SDK elsewhere, you must provide "firebaseAdminInitConfig" to next-firebase-auth.' + 'Missing firebase-admin credentials in next-firebase-auth. Set "firebaseAdminInitConfig", "useFirebaseAdminDefaultCredential", or initialize firebase-admin yourself.' ) }) diff --git a/src/initFirebaseAdminSDK.js b/src/initFirebaseAdminSDK.js index 3140e50d..c8bb6704 100644 --- a/src/initFirebaseAdminSDK.js +++ b/src/initFirebaseAdminSDK.js @@ -6,17 +6,20 @@ import { getConfig } from 'src/config' const initFirebaseAdminSDK = () => { if (!admin.apps.length) { - const { firebaseAdminInitConfig } = getConfig() - if (!firebaseAdminInitConfig) { + const { firebaseAdminInitConfig, useFirebaseAdminDefaultCredential } = + getConfig() + if (!firebaseAdminInitConfig && !useFirebaseAdminDefaultCredential) { throw new Error( - 'If not initializing the Firebase admin SDK elsewhere, you must provide "firebaseAdminInitConfig" to next-firebase-auth.' + 'Missing firebase-admin credentials in next-firebase-auth. Set "firebaseAdminInitConfig", "useFirebaseAdminDefaultCredential", or initialize firebase-admin yourself.' ) } admin.initializeApp({ ...firebaseAdminInitConfig, - credential: admin.credential.cert({ - ...firebaseAdminInitConfig.credential, - }), + credential: useFirebaseAdminDefaultCredential + ? admin.credential.applicationDefault() + : admin.credential.cert({ + ...firebaseAdminInitConfig.credential, + }), }) } return admin From 380e99005e3690a6df5dd09d0f2cbb98472ef391 Mon Sep 17 00:00:00 2001 From: Kevin Jennison Date: Fri, 10 Dec 2021 15:20:31 -0500 Subject: [PATCH 2/3] Update README.md --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f1d1f1ca..83023910 100644 --- a/README.md +++ b/README.md @@ -414,15 +414,35 @@ export default withAuthUser()(Artist) See an [example config here](#example-config). Provide the config when you call `init`. -**authPageURL**: The default URL to navigate to when `withAuthUser` or `withAuthUserTokenSSR` need to redirect to login. Can be a string or a function that receives `{ ctx }` and returns a URL. Optional unless using the `AuthAction.REDIRECT_TO_LOGIN` auth action. +#### authPageURL -**appPageURL**: The default URL to navigate to when `withAuthUser` or `withAuthUserTokenSSR` need to redirect to the app. Can be a string or a function that receives `{ ctx }` and returns a URL. Optional unless using the `AuthAction.REDIRECT_TO_APP` auth action. +`String|Function` -**loginAPIEndpoint**: The API endpoint this module will call when the auth state changes for an authenticated Firebase user. Must be set unless `tokenChangedHandler` is set. +The default URL to navigate to when `withAuthUser` or `withAuthUserTokenSSR` need to redirect to login. Can be a string or a function that receives `{ ctx }` and returns a URL. Optional unless using the `AuthAction.REDIRECT_TO_LOGIN` auth action. -**logoutAPIEndpoint**: The API endpoint this module will call when the auth state changes for an unauthenticated Firebase user. Must be set unless `tokenChangedHandler` is set. +#### appPageURL -**tokenChangedHandler**: A callback that runs when the auth state changes for a particular user. Use this if you want to customize how your client-side app calls your login/logout API endpoints (for example, to use a custom fetcher or add custom headers). `tokenChangedHandler` receives an `AuthUser` as an argument and is called when the user's ID token changes, similarly to Firebase's `onIdTokenChanged` event. +`String|Function` + +The default URL to navigate to when `withAuthUser` or `withAuthUserTokenSSR` need to redirect to the app. Can be a string or a function that receives `{ ctx }` and returns a URL. Optional unless using the `AuthAction.REDIRECT_TO_APP` auth action. + +#### loginAPIEndpoint + +`String` + +The API endpoint this module will call when the auth state changes for an authenticated Firebase user. Must be set unless `tokenChangedHandler` is set. + +#### logoutAPIEndpoint + +`String` + +The API endpoint this module will call when the auth state changes for an unauthenticated Firebase user. Must be set unless `tokenChangedHandler` is set. + +#### tokenChangedHandler + +`Function` + +A callback that runs when the auth state changes for a particular user. Use this if you want to customize how your client-side app calls your login/logout API endpoints (for example, to use a custom fetcher or add custom headers). `tokenChangedHandler` receives an `AuthUser` as an argument and is called when the user's ID token changes, similarly to Firebase's `onIdTokenChanged` event. If this callback is specified, user is responsible for: @@ -432,11 +452,17 @@ If this callback is specified, user is responsible for: Cannot be set with `loginAPIEndpoint` or `logoutAPIEndpoint`. -**firebaseAuthEmulatorHost**: The host and port for the local [Firebase Auth Emulator](https://firebase.google.com/docs/emulator-suite/connect_auth#admin_sdks). If this value is set, the auth emulator will be initialized with the provided host and port. +#### firebaseAuthEmulatorHost + +`String` + +The host and port for the local [Firebase Auth Emulator](https://firebase.google.com/docs/emulator-suite/connect_auth#admin_sdks). If this value is set, the auth emulator will be initialized with the provided host and port. Must be exactly the same as the value of the `FIREBASE_AUTH_EMULATOR_HOST` environment variable, e.g., `localhost:9099`. -#### **firebaseAdminInitConfig** +#### firebaseAdminInitConfig + +`Object` Configuration passed to `firebase-admin`'s [`initializeApp`](https://firebase.google.com/docs/admin/setup#initialize-sdk). It should contain a `credential` property (a plain object) and a `databaseURL` property. **Required** unless you initialize `firebase-admin` yourself before initializing `next-firebase-auth`. @@ -456,15 +482,23 @@ The `firebaseAdminInitConfig.credential.privateKey` cannot be defined on the cli > > See [this Vercel issue](https://github.com/vercel/vercel/issues/749#issuecomment-707515089) for more information. -**useFirebaseAdminDefaultCredential** +#### useFirebaseAdminDefaultCredential + +`Boolean` + +When true, `firebase-admin` will use the Google Cloud application default credentials during [`initializeApp`](https://firebase.google.com/docs/admin/setup#initialize-sdk). + +**Note**: When true, default credentials will override values passed to `firebaseAdminInitConfig.credential`. + +#### firebaseClientInitConfig -A boolean that when set to true, the application default credential will be passed to `firebase-admin`'s [`initializeApp`](https://firebase.google.com/docs/admin/setup#initialize-sdk). +`Object` -**NOTE**: When set to true default credentials will override values passed to `firebaseAdminInitConfig.credential` +Configuration passed to the Firebase JS SDK's [`initializeApp`](https://firebase.google.com/docs/reference/node/firebase#initializeapp). The `firebaseClientInitConfig.apiKey` value is **always required**. Other properties are required unless you initialize the `firebase` app yourself before initializing `next-firebase-auth`. -**firebaseClientInitConfig**: Configuration passed to the Firebase JS SDK's [`initializeApp`](https://firebase.google.com/docs/reference/node/firebase#initializeapp). The "firebaseClientInitConfig.apiKey" value is always **required**. Other properties are required unless you initialize the `firebase` app yourself before initializing `next-firebase-auth`. +#### cookies -#### **cookies** +`Object` Settings used for auth cookies. We use [`cookies`](https://github.com/pillarjs/cookies) to manage cookies. From fe77715c802c1e4aa7b65b30831c816415425dfc Mon Sep 17 00:00:00 2001 From: Kevin Jennison Date: Fri, 10 Dec 2021 16:37:37 -0500 Subject: [PATCH 3/3] Change example app cookies to use SameSite=Lax --- example/utils/initAuth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/utils/initAuth.js b/example/utils/initAuth.js index 50cd5e92..6f225764 100644 --- a/example/utils/initAuth.js +++ b/example/utils/initAuth.js @@ -94,7 +94,7 @@ const initAuth = () => { maxAge: TWELVE_DAYS_IN_MS, overwrite: true, path: '/', - sameSite: 'strict', + sameSite: 'lax', secure: process.env.NEXT_PUBLIC_COOKIE_SECURE === 'true', signed: true, },