gladly-team / next-firebase-auth

Simple Firebase authentication for all Next.js rendering strategies
https://nfa-example-git-v1x-gladly-team.vercel.app/
MIT License
1.34k stars 291 forks source link

Frontend compilation fails with child_process module not found #116

Closed jmif closed 3 years ago

jmif commented 3 years ago

Describe the bug I've followed the config examples in the repo but the client JS fails to compile with the following error:

error - ./node_modules/google-auth-library/build/src/auth/googleauth.js:17:0
Module not found: Can't resolve 'child_process'

My init auth file (note that I do not supply server side admin config, I initialize admin SDK outside of this file before importing this):

import { init } from 'next-firebase-auth';
import { COOKIE_SECRET } from './config';

export default function initAuth(): void {
  init({
    authPageURL: '/auth',
    appPageURL: '/',
    loginAPIEndpoint: '/api/auth/login',
    logoutAPIEndpoint: '/api/auth/logout',

    firebaseClientInitConfig: {
      apiKey: 'MyExampleAppAPIKey123', // required
      authDomain: 'my-example-app.firebaseapp.com',
      databaseURL: 'https://my-example-app.firebaseio.com',
      projectId: 'my-example-app-id',
    },

    cookies: {
      name: 'auth-data', // required
      // Keys are required unless you set `signed` to `false`.
      // The keys cannot be accessible on the client side.
      keys: [COOKIE_SECRET],
      httpOnly: true,
      maxAge: 12 * 60 * 60 * 24 * 1000, // twelve days
      overwrite: true,
      path: '/',
      sameSite: 'strict',
      secure: process.env.NODE_ENV === 'production',
      signed: true,
    },
  });
}

My _app.tsx file:

import App from 'next/app';

import { NotificationsProvider } from 'features/notifications/context';
import initAuth from '../lib/auth';

import './fonts.css';
import './global.css';

initAuth();

class MyApp extends App {
  componentDidMount(): void {
    const style = document.getElementById('server-side-styles');

    if (style) {
      style.parentNode?.removeChild(style);
    }
  }

  render(): JSX.Element {
    const { Component, pageProps } = this.props;
    return (
      <NotificationsProvider>
        <Component {...pageProps} />
      </NotificationsProvider>
    );
  }
}

export default MyApp;

If I remove the initAuth import and method call then compilation fails but I'm not able to render pages that require auth, I get an error message saying client side auth needs to be configured.

Looks like this is also referenced here. From what I can tell something in the dependency tree of firebase auth is pulling in a server side auth module which imports child_process. I've also tried supplying the firebaseAdminInitConfig field but the same error occurs.

Version

All dependency versions from package json:

    "axios": "^0.21.1",
    "babel-plugin-superjson-next": "^0.1.9",
    "blurhash": "^1.1.3",
    "classnames": "^2.2.6",
    "email-validator": "^2.0.4",
    "firebase": "^8.3.1",
    "firebase-admin": "^9.5.0",
    "firebase-functions": "^3.13.2",
    "jss": "^10.5.1",
    "lodash": "^4.17.20",
    "mixpanel-browser": "^2.40.1",
    "next": "10.0.9",
    "next-firebase-auth": "^0.13.0-alpha.0",
    "next-i18next": "^7.0.1",
    "pretty-ms": "^7.0.1",
    "react": "^16.12.0",
    "react-blurhash": "^0.1.3",
    "react-dom": "^16.12.0",
    "react-jss": "^10.5.1",
    "superjson": "^1.4.1"
kmjennison commented 3 years ago

Yeah, it looks like the Firebase admin module is being included in client-side code somewhere. Can you try removing firebase-admin from everywhere except API routes? Or, where you're importing firebase-admin, try checking the code with the Next.js code elimination app to ensure it's not bundled for the client.

jmif commented 3 years ago

Yep that was the problem, we had a call to firebase.config() to pull the cookie secret. We're deployed to firebase functions right now so we can't set environment variables to pass the secret. Ideally we'd only run this code server side, is there a way to achieve this? Only thing I can think of right now is two different configs, one for client and one for server:

server.ts (imported in server code)

export default function initAuth(): void {
  init({
    ...commonConfig,

    cookies: {
      name: 'auth', // required
      // Keys are required unless you set `signed` to `false`.
      // The keys cannot be accessible on the client side.
      keys: [COOKIE_SECRET],
      httpOnly: true,
      maxAge: 12 * 60 * 60 * 24 * 1000, // twelve days
      overwrite: true,
      path: '/',
      sameSite: 'strict',
      secure: process.env.NODE_ENV === 'production',
      signed: true,
    },
  });
}

client.ts (imported in _app.tsx)

export default function initAuth(): void {
  init({
    ...commonConfig,

    cookies: {
      name: 'auth', // required
      signed: false,
    },
  });
}
kmjennison commented 3 years ago

Yes, initializing separately for client and server should work as long as you take care to eliminate the server code from the client bundle. You should also be able to init once and conditionally set the config values based on the client/server context, even without using environment variables.

It looks like Firebase functions do support environment variables. I haven't used this before so not sure if it's helpful for your use case.