Home > Net >  Is it insecure and bad practice to expose your environment variables to the browser in NextJS?
Is it insecure and bad practice to expose your environment variables to the browser in NextJS?

Time:01-31

I am currently attempting to set up Firebase v9 authentication in a NextJS app I am working on. I was originally trying to use Next's server-side environmental variables for my Firebase config but noticed I kept getting undefined for all my environment variables. But then I updated my Firebase config to use NEXT_PUBLIC_ and then it was work fine. So I guess my questsions are:

  1. Is secure to expose your Firebase Config variables to the browser?
  2. If it is not secure. How do you ensure that the app is initialized server-side and then consumed client-side? (links any specific guides or articles would be great appreciated)

Below I have provided my firebase config file, AuthContext Provider, and Login page in that order.

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET_URL,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
  measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};

const app = initializeApp(firebaseConfig);
export const auth = getAuth();
import { createContext, useContext, useEffect, useState } from 'react';
import {
  onAuthStateChanged,
  signInWithEmailAndPassword,
  signOut,
} from 'firebase/auth';
import { auth } from '@/lib/firebaseApp';

const AuthContext = createContext({});

export const useAuth = () => useContext(AuthContext);

export function AuthContextProvider({ children }) {
  const [user, setUser] = useState({ email: null, uid: null });
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      if (user) {
        setUser({
          email: user.email,
          uid: user.uid,
        });
      } else {
        setUser({ email: null, uid: null });
      }
    });
    setLoading(false);
    return () => unsubscribe();
  }, []);

  function login(email, password) {
    return signInWithEmailAndPassword(auth, email, password);
  }

  async function logout() {
    setUser({ email: null, uid: null });
    await signOut(auth);
  }

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {loading ? null : children}
    </AuthContext.Provider>
  );
}
import { useState } from 'react';
import { useRouter } from 'next/router';

import { Button } from '@/components';
import { useAuth } from '@/context/AuthContext';

export default function Login() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const { login } = useAuth();
  const router = useRouter();

  async function handleSubmit(event) {
    event.preventDefault();
    try {
      await login(email, password);
      router.push('/dashboard');
    } catch (error) {
      console.log(error.message);
    }
  }

  return (
    <div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
      <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
        <div className="mx-auto w-11/12 rounded-2xl border border-zinc-100 p-6 py-8 px-4 dark:border-zinc-700/40 sm:px-10 md:w-full">
          <form className="space-y-6" onSubmit={(event) => handleSubmit(event)}>
            <div>
              <label
                htmlFor="email"
                className="block text-sm font-semibold text-zinc-900 dark:text-zinc-100"
              >
                Email address
              </label>
              <div className="mt-1">
                <input
                  id="email"
                  name="email"
                  type="email"
                  autoComplete="email"
                  required
                  className="block w-full appearance-none rounded-md border border-zinc-900/10 bg-white px-3 py-2 shadow-sm shadow-zinc-800/5 placeholder:text-zinc-400 focus:border-teal-500 focus:outline-none focus:ring-4 focus:ring-teal-500/10 dark:border-zinc-700 dark:bg-zinc-700/[0.15] dark:text-zinc-200 dark:placeholder:text-zinc-500 dark:focus:border-teal-400 dark:focus:ring-teal-400/10 sm:text-sm"
                  onChange={(e) => setEmail(e.target.value)}
                />
              </div>
            </div>
            <div>
              <label
                htmlFor="password"
                className="block text-sm font-semibold text-zinc-900 dark:text-zinc-100"
              >
                Password
              </label>
              <div className="mt-1">
                <input
                  id="password"
                  name="password"
                  type="password"
                  autoComplete="current-password"
                  required
                  className="block w-full appearance-none rounded-md border border-zinc-900/10 bg-white px-3 py-2 shadow-sm shadow-zinc-800/5 placeholder:text-zinc-400 focus:border-teal-500 focus:outline-none focus:ring-4 focus:ring-teal-500/10 dark:border-zinc-700 dark:bg-zinc-700/[0.15] dark:text-zinc-200 dark:placeholder:text-zinc-500 dark:focus:border-teal-400 dark:focus:ring-teal-400/10 sm:text-sm"
                  onChange={(e) => setPassword(e.target.value)}
                />
              </div>
            </div>
            <div className="flex w-full justify-center">
              <Button type="submit" className="w-10/12 px-6 py-4 md:w-5/6">
                Sign In
              </Button>
            </div>
          </form>
        </div>
      </div>
    </div>
  );
}

CodePudding user response:

Why you cant access your .env variables

You are currently accessing your enviroment variables on the client side. This functionality is not needed hence doesnt exists by default, since everything on a client side is visible to everyone. You could paste your API-keys directly in plain text. It's the same thing.

Firebase Admin vs client side

There's two choices to firebase. Either you use the client side SDK where the API-keys are public, and can be shown in public without any problem. You write your own rules in the firestore console. (This is what you seem to be using)

Important If you publish your app, you must set the firestore rules to locked mode, and only allow read/write if you specifically have written a rule for it.

Otherwise you can use firebase admin SDK which should only be run on server side. Then you communicate with firebase via Nextjs API-routes. The API-keys to initialize this app is absolutely private, and shown not be exposed. If exposed, anyone can gain total control of your project

You can read more about firebase admin SDK here: https://firebase.google.com/docs/admin/setup

Conclusion

You are using firebase sdk for the client side, and hence your API-keys can safely be exposed. But dont forget to set your rules for who can read/write. If you have your project in "test mode" on release, anyone can gain control of your app

  • Related