Authentication is crucial in web applications today. It’s a feature that many developers have had to implement in the past. Thankfully, many libraries have made this job easier by offering many built-in functionalities. In particular, Firebase is an excellent tool for handling user management and authentication.
In this tutorial, we will cover how to implement authentication using Firebase.
What is Firebase?
Acquired by Google in 2014, Firebase is a platform offering a suite of products including but not limited to:
- Realtime Database
- Authentication
- Cloud Messaging
These products allow developers to create and run applications easily and quickly.
Creating a Firebase account
Before you write a line of code, you will need a Firebase account. Head here to create one.
In Firebase, if you want API keys, you will need to create apps. These apps have to belong to projects. As a result, if you don’t already have a project set up, you need to create one. Once you do, create an app to get your keys.

Now, click on the settings icon right beside Project Overview (in the top left part of your screen). In Project Settings and under General, you should see your app with its config.
Before you head to your code, you will need to enable the sign-in methods you want to use. To do so, click on Authentication and then Sign in methods. Each one has different configurations, but for the sake of this tutorial, I will be focusing on the traditional email/password method.

Adding local environments to Next.js
Now that you have your keys, it’s time to add them to your Next.js project.
Tip: If you don’t have one already created, fret not. This command will get you going:
npx create-next-app # or yarn create next-app
A Next.js project automatically ignores .env.local
thanks to its .gitignore
file, so you can copy/paste your keys and not worry that they will be committed to GitHub accidentally.
NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY=<YOUR_API_KEY> NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=<YOUR_DOMAIN> NEXT_PUBLIC_FIREBASE_PROJECT_ID=<YOUR_PROJECT_ID>
Don’t forget: In Next.js, the convention for naming environment variables is that they have to start with NEXT_PUBLIC.
Installing Firebase
Go ahead and install the Firebase library.
npm install --save Firebase # or yarn add Firebase
Creating a Firebase instance in Next.js
Great! The library is installed and your API keys are set up. Time to use those keys to create a Firebase instance. Of course, Firebase comes with many useful tools, but for the sake of this article, we will only focus on authentication. As a result, you will only need Firebase/auth
and the apiKey
, authDomain
, and projectId
credentials.
import Firebase from 'Firebase/app'; import 'Firebase/auth'; const FirebaseCredentials = { apiKey: process.env.NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY, authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID } // if a Firebase instance doesn't exist, create one if (!Firebase.apps.length) { Firebase.initializeApp(FirebaseCredentials) } export default Firebase;
Listening for Firebase changes
The first thing you will need is an authUser
that you can access throughout your app. This variable is not only helpful for user management but also for redirecting routes accordingly.
For example, if authUser
is null, meaning the user hasn’t logged in, when that person tries to access a protected route (say, a dashboard), you should redirect them to the login page.
Thankfully, Firebase.auth
keeps track of the state and comes with a built-in function called onAuthStateChanged
that allows you to listen for state changes.
When the state changes, format the user depending on your needs, and, finally, set it to your authUser
variable. Use the loading
variable to indicate whether Firebase is fetching data or not.
import { useState, useEffect } from 'react' import Firebase from './Firebase'; const formatAuthUser = (user) => ({ uid: user.uid, email: user.email }); export default function useFirebaseAuth() { const [authUser, setAuthUser] = useState(null); const [loading, setLoading] = useState(true); const authStateChanged = async (authState) => { if (!authState) { setAuthUser(null) setLoading(false) return; } setLoading(true) var formattedUser = formatAuthUser(authState); setAuthUser(formattedUser); setLoading(false); }; // listen for Firebase state change useEffect(() => { const unsubscribe = Firebase.auth().onAuthStateChanged(authStateChanged); return () => unsubscribe(); }, []); return { authUser, loading }; }
Creating a user context
To access authUser
and loading variables throughout your app, you will be using the Context API.
Tip: Unfamiliar with React Context? Don’t hesitate to check out the official docs.
First, create your context object with createContext
with a default value (authUser
as null
and loading as true
). Then, get the actual authUser
and loading
variables from useFirebaseAuth
and pass it to the provider component.
You should also add a custom hook, in this case, useAuth
, to access the current context value.
More great articles from LogRocket:
- Don't miss a moment with The Replay, a curated newsletter from LogRocket
- Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
- Use React's useEffect to optimize your application's performance
- Switch between multiple versions of Node
- Discover how to animate your React app with AnimXYZ
- Explore Tauri, a new framework for building binaries
- Advisory boards aren’t just for executives. Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
import { createContext, useContext, Context } from 'react' import useFirebaseAuth from '../lib/useFirebaseAuth'; const authUserContext = createContext({ authUser: null, loading: true }); export function AuthUserProvider({ children }) { const auth = useFirebaseAuth(); return <authUserContext.Provider value={auth}>{children}</authUserContext.Provider>; } // custom hook to use the authUserContext and access authUser and loading export const useAuth = () => useContext(authUserContext);
Then, in our _app.js
, wrap this provider around your application. This ensures that the children components will be able to access your user context.
import { AuthUserProvider } from '../context/AuthUserContext'; function MyApp({ Component, pageProps }) { return <AuthUserProvider><Component {...pageProps} /></AuthUserProvider> } export default MyApp
Creating protected routes
Protected routes are pages or sections of your app that should only be accessed by certain users. In this case, only logged-in users should access this content. To set this up, get the authUser
and loading
from your custom useAuth()
hook.
With these variables in place, check if Firebase is still fetching data (i.e., loading is true
), and, if not, whether authUser
is null
. If that is the case, then the user isn’t logged in and you should redirect them to the login page.
Test it in your app and make sure the redirection is happening correctly.
import { useEffect } from 'react'; import { useRouter } from 'next/router'; import { useAuth } from '../context/AuthUserContext'; import {Container, Row, Col} from 'reactstrap'; const LoggedIn = () => { const { authUser, loading } = useAuth(); const router = useRouter(); // Listen for changes on loading and authUser, redirect if needed useEffect(() => { if (!loading && !authUser) router.push('/') }, [authUser, loading]) return ( //Your logged in page ) } export default LoggedIn;
Adding login, sign-up, and sign-out functionalities in Next.js
Now, let’s move on to the juicy bit. One great thing about Firebase is that it comes with many built-in functions for signing in, creating users, and signing out.
So, let’s add them to the useFirebaseAuth function. Use Firebase.auth()
to access the different functions (signInWithEmailAndPassword
, createUserWithEmailAndPassword
, and signOut
):
export default function useFirebaseAuth() { // ... const clear = () => { setAuthUser(null); setLoading(true); }; const signInWithEmailAndPassword = (email, password) => Firebase.auth().signInWithEmailAndPassword(email, password); const createUserWithEmailAndPassword = (email, password) => Firebase.auth().createUserWithEmailAndPassword(email, password); const signOut = () => Firebase.auth().signOut().then(clear); useEffect(() => { const unsubscribe = Firebase.auth().onAuthStateChanged(authStateChanged); return () => unsubscribe(); }, []); return { authUser, loading, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut }; }
Don’t forget to update your default value in your context file.
const authUserContext = createContext({ authUser: null, loading: true, signInWithEmailAndPassword: async () => {}, createUserWithEmailAndPassword: async () => {}, signOut: async () => {} }); export function AuthUserProvider({ children }) { const auth = useFirebaseAuth(); return <authUserContext.Provider value={auth}>{children}</authUserContext.Provider>; }
Creating the sign-up page
In your sign-up page, use your useAuth hook
to retrieve your function for creating a user once again. createUserWithEmailAndPassword
takes two parameters: email and password.
After finishing form validation, call this function. If it returns successfully with an authUser
, then you can redirect the user accordingly.
import { useState } from 'react'; import { useRouter } from 'next/router'; import { useAuth } from '../context/AuthUserContext'; import {Container, Row, Col, Button, Form, FormGroup, Label, Input, Alert} from 'reactstrap'; const SignUp = () => { const [email, setEmail] = useState(""); const [passwordOne, setPasswordOne] = useState(""); const [passwordTwo, setPasswordTwo] = useState(""); const router = useRouter(); const [error, setError] = useState(null); const { createUserWithEmailAndPassword } = useAuth(); const onSubmit = event => { setError(null) //check if passwords match. If they do, create user in Firebase // and redirect to your logged in page. if(passwordOne === passwordTwo) createUserWithEmailAndPassword(email, passwordOne) .then(authUser => { console.log("Success. The user is created in Firebase") router.push("/logged_in"); }) .catch(error => { // An error occurred. Set error message to be displayed to user setError(error.message) }); else setError("Password do not match") event.preventDefault(); }; return ( <Container className="text-center custom-container"> <Row> <Col> <Form className="custom-form" onSubmit={onSubmit}> { error && <Alert color="danger">{error}</Alert>} <FormGroup row> <Label for="signUpEmail" sm={4}>Email</Label> <Col sm={8}> <Input type="email" value={email} onChange={(event) => setEmail(event.target.value)} name="email" id="signUpEmail" placeholder="Email" /> </Col> </FormGroup> <FormGroup row> <Label for="signUpPassword" sm={4}>Password</Label> <Col sm={8}> <Input type="password" name="passwordOne" value={passwordOne} onChange={(event) => setPasswordOne(event.target.value)} id="signUpPassword" placeholder="Password" /> </Col> </FormGroup> <FormGroup row> <Label for="signUpPassword2" sm={4}>Confirm Password</Label> <Col sm={8}> <Input type="password" name="password" value={passwordTwo} onChange={(event) => setPasswordTwo(event.target.value)} id="signUpPassword2" placeholder="Password" /> </Col> </FormGroup> <FormGroup row> <Col> <Button>Sign Up</Button> </Col> </FormGroup> </Form> </Col> </Row> </Container> ) } export default SignUp;
Adding a sign-out button
Signing out is also very straightforward. Grab the signOut()
function from useAuth()
and add it to a button or a link.
import { useEffect } from 'react'; import { useRouter } from 'next/router'; import { useAuth } from '../context/AuthUserContext'; import {Container, Row, Col, Button} from 'reactstrap'; const LoggedIn = () => { const { authUser, loading, signOut } = useAuth(); const router = useRouter(); // Listen for changes on loading and authUser, redirect if needed useEffect(() => { if (!loading && !authUser) router.push('/') }, [authUser, loading]) return ( <Container> // ... <Button onClick={signOut}>Sign out</Button> // ... </Container> ) } export default LoggedIn;
Creating a login page
And finally, the login functionality! It’s exactly the same as the previous two. Retrieve signInWithEmailAndPassword()
from useAuth()
and pass in the user’s email and password. If they are correct, redirect the user, and, if not, display the correct error message.
import { useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { useAuth } from '../context/AuthUserContext'; import {Container, Row, Col, Button, Form, FormGroup, Label, Input, Alert} from 'reactstrap'; export default function Home() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(null); const router = useRouter(); const { signInWithEmailAndPassword } = useAuth(); const onSubmit = event => { setError(null) signInWithEmailAndPassword(email, password) .then(authUser => { router.push('/logged_in'); }) .catch(error => { setError(error.message) }); event.preventDefault(); }; return ( <Container className="text-center" style={{ padding: '40px 0px'}}> <Row> <Col> <h2>Login</h2> </Col> </Row> <Row style={{maxWidth: '400px', margin: 'auto'}}> <Col> <Form onSubmit={onSubmit}> { error && <Alert color="danger">{error}</Alert>} <FormGroup row> <Label for="loginEmail" sm={4}>Email</Label> <Col sm={8}> <Input type="email" value={email} onChange={(event) => setEmail(event.target.value)} name="email" id="loginEmail" placeholder="Email" /> </Col> </FormGroup> <FormGroup row> <Label for="loginPassword" sm={4}>Password</Label> <Col sm={8}> <Input type="password" name="password" value={password} onChange={(event) => setPassword(event.target.value)} id="loginPassword" placeholder="Password" /> </Col> </FormGroup> <FormGroup row> <Col> <Button>Login</Button> </Col> </FormGroup> <FormGroup row> <Col> No account? <Link href="/sign_up">Create one</Link> </Col> </FormGroup> </Form> </Col> </Row> </Container> ) }
Conclusion
In this tutorial, we covered how to create a Firebase account, project, and app. Then, we learned how to use React Context to create a user context. To this context, we added user and loading variables along with logging, signing up, and signing out functions. Finally, we used those functions to implement authentication in our Next.js app thanks to Firebase!
The complete code for this tutorial is available on GitHub.
I think this blog will be more helpful if you mention the files and directories in which we have to copy + paste/ add these code snippets 🙂
Yes, it will be more helpful if you can a Git repository to refer. Thank you.
Hey Omib! In one of the previous comments, from back in June 2021, I added a comment and linked a git repository. Joel also added a comment on how to handle the newer version (aka 9) which should be very helpful.
We just added the repo link to the post as well. Thanks!
Hey Omama, this is Marie, the author. I unfortunately don’t have access to the blog post but I just created a public repository with the code: https://github.com/mariesta/nextjs-auth-with-firebase.
Hope this helps.
I had been trying to set up firebase. I had no idea that it was the latest version, Version 9. I could not for the life of me figure out why I was running into so many bugs. I had used the same set up numerous times.
Finally, I dove deeper into the docs.
For version 9 plus
import firebase from ‘firebase/compat/app’;
import ‘firebase/compat/auth’;
import ‘firebase/compat/firestore’;
Then it worked like a charm
Just don’t want to see anyone else burn a day banging their head against the wall wondering why it was running into so many bugs.
Thank you for this, Joel! Indeed, this article was written before version 9 was out.
Thanks for this post but i have a question: if in situation the app has multiple page which is need to auth. Will you duplicate code of useEffect? 😀
Unfortunately, Next.js doesn’t have an elegant way to handle auth and non-auth routes (or at least not one that I know of). The best way would be to create an isAuthenticated hook that does the listening on rendering. It’s not ideal but it’s all I got.
Thank you very much again
Hi Marie,
I must say that your blog is really helpful. I found it helpful in context of a regular react project.
I am now learning NEXT JS and was reading the pre-rendering documentation, getStaticProps and getStaticPages. This made me wonder how pages will be pre-rendered if we have some sections to render that depend on authentication.
Can you also add an ‘advanced’ section to your blog that covers authentication and pre-rendering.
Thanks.
An interesting point! I hadn’t considered that approach, but I will definitely add it to my list of topics. Thank you for your suggestion
This is an excellent post. An important note though that it’s based on version 8.3.3 of the Firebase libraries. Lots of breaking changes in 9.x with respect to how to locate and call the auth functions.
npm uninstall firebase
npm install [email protected] —save
Thanks for such a well written and informative piece!
Glad you liked it and thank you for your note! I am thinking to write a following post which would cover changes from 9.x.
How can I create a protected route which is using SSR
Please refer to my answer to Nguyen back in July 7th (aka third posts above you). He asked a similar question and I replied there.
Hello Marie, Really awesome guide! Thought I’m getting an issue where createUserWithEmailAndPassword() runs, logs as success and the same function inside useFirebaseAuth() never gets executed (= no user actually gets created). Any chance you’ve come across a similar issue? Appreciate it!
Hey Ruben! Glad to hear you liked it. As for your issue, it doesn’t ring a bell but there could be a thousand reason why it is not working and it’s hard to tell why without seeing the code. If you are having some issues debugging, I suggest you post a question with a link to your code on StackOverflow and tagged it firebase. Good luck!
How to use this for conditional routing? My index page is marketing page. I want to automatically redirect the user to home page if he loggedin. How to do that?
Unfortunately, Next.js isn’t like React (and React Router) where you can easily define routes and redirect when needed. The only workaround would be to have a check in your index.js if the user is authenticated and redirect if he is. Something like this:
const { authUser, loading } = useAuth();
const router = useRouter();
// Redirect if user is authenticated
useEffect(() => {
if (!loading && authUser)
router.push(‘/homepage’)
}, [authUser, loading])
Thanks for the project. I am just learning nextjs, firebase, and react and this nailed it for me. I updated the project to firebase v9 and I would be more than happy to submit a PR. I didn’t see these comments until today so I created an issue against the project in github.
Hey Dan! Thanks for the comment and glad this article was helpful. I am replying to your issue on the PR. 🙂
Hi Marie,
But I have a question, why use jsx suffixed files instead of js?( useFirebaseAuth.jsx, logged_in.jsx , sign_up.jsx )
I am a beginner T-T
Hey Cjoy!
Honestly, there are often interchangeable. Your bundler/transpiler will be the one handling the file content and most handle both. Whether you decide to use .js or .jsx is up to you and here is a discussion to discuss different point of view: https://github.com/facebook/create-react-app/issues/87
Hope this helps!
Why do you not provide the screenshot of the output???
Hey,
Great lessen! I just need to know some basic details. How would I actually code the app to only accept certain emails? Say I have 2 admin for a blog site. How would I set it to deny some emails. Thanks in advance.