Logging users out with Firebase Auth and Next.js

Learn how to sign users out with Firebase Auth and Next.js, and automatically redirect the user out of the page

In this post, we will learn how to use Firebase Auth to log users out and redirect them out of the application with Next.js.

To do so, we will use a component wrapper named GuardedPage that you can use to wrap your page components that are protected from non-authenticated users.

To do so, let's create a component named AuthRedirectListener, which takes care of setting up a listener with Firebase Auth to detect when the user logs out.

Here is the usage of our component:

<AuthRedirectListener whenSignedOut='/auth/login'>
{children}
</AuthRedirectListener>

This component is private and will be part of another component named GuardedPage, which is public and intended to be used for wrapping page components.

In fact, the GuardedPage component checks that the code is running within the browser because it uses the web Firebase SDK, and thus we are unable to use server-side rendering; if rendering on the server, we simply render the children components instead of AuthRedirectListener.

<GuardedPage whenSignedOut='/auth/login'>
<MyPrivateComponent />
</GuardedPage>

Detecting the user signed out with Firebase Auth

To detect when the user signed out, we can use onAuthStateChanged, a function that will call a callback function when the user's authentication state changes.

To understand that the user logged out, we check if the user parameter is undefined or not.

Let's take a look at how it's done:

const listener = auth.onAuthStateChanged((user) => {
// make sure the user provided a URL where to
// redirect the users after they sign out
const shouldLogOut = !user && whenSignedOut;
// log user out if user is falsy
// and if the consumer provided a route to redirect the user
if (shouldLogOut) {
const currentPath = window.location.pathname;
const isNotCurrentPage = currentPath !== whenSignedOut;
const navigateToSignOutPage = () => {
window.location.assign(whenSignedOut);
};
// we then redirect the user to the page
// specified in the props of the component
if (isNotCurrentPage) {
navigateToSignOutPage();
}
}
});

As you can see from the snippet above, we check that the parameter user is undefined to understand if the user is still signed-in.

When the user parameter is undefined and when the consumer provided a URL where to take the user, we make a browser redirect towards the URL provided with the follow line:

window.location.assign(whenSignedOut);

The GuardedPage component to guard protected components in a Next.js application

Of course, this code should run:

  1. The user has logged in
  2. When the page renders and when the dependencies change

That means, we have to use a useEffect hook to call this side-effect, so that it will only be executed at page load (and avoid needless multiple callbacks) or if the dependencies change (but they shouldn't).

To check if the user signed in, we use the following Hook:

import { useAuth, useSigninCheck } from 'reactfire';
const { status } = useSigninCheck();
useEffect(() => {
// this should run once and only on success
// i.e. when the user signed in!
if (status !== 'success') {
return;
}
// code to execute here...
});

Below, you can find the complete snippet of the component GuardedPage:

import { useEffect } from 'react';
import { useAuth, useSigninCheck } from 'reactfire';
const AuthRedirectListener: React.FCC<{
whenSignedOut?: string;
}> = ({ children, whenSignedOut }) => {
const auth = useAuth();
const { status } = useSigninCheck();
useEffect(() => {
// this should run once and only on success
if (status !== 'success') {
return;
}
// keep this running for the whole session
// unless the component was unmounted, for example, on log-outs
const listener = auth.onAuthStateChanged((user) => {
const shouldLogOut = !user && whenSignedOut;
// log user out if user is falsy
// and if the consumer provided a route to redirect the user
if (shouldLogOut) {
clearFirestoreCache();
const currentPath = window.location.pathname;
const isNotCurrentPage = currentPath !== whenSignedOut;
const navigateToSignOutPage = () => {
window.location.assign(whenSignedOut);
};
// we then redirect the user to the page
// specified in the props of the component
if (isNotCurrentPage) {
navigateToSignOutPage();
}
}
});
// destroy listener on un-mounts
return () => listener();
}, [auth, status, whenSignedOut]);
return <>{children}</>;
};
export default function GuardedPage({
children,
whenSignedOut,
}: React.PropsWithChildren<{
whenSignedOut?: string;
}>) {
const shouldActivateListener = isBrowser();
// we only activate the listener if
// we are rendering in the browser
if (!shouldActivateListener) {
return <>{children}</>;
}
return (
<AuthRedirectListener whenSignedOut={whenSignedOut}>
{children}
</AuthRedirectListener>
);
}

Logging Users out with Firebase Auth

Now that our component will automatically redirect the user out when they sign out, let's see how to create a logout button with React. To do so, we create a smart button component that imports the Firebase Auth SDK and calls the method to sign users out:

function LogoutButton() {
const auth = useAuth();
const onSignOut = useCallback(() => {
void auth.signOut();
}, [auth]);
return (
<button onClick={onSignOut}>Sign Out</button>
);
}

Your page will look somewhat similar to the below:

  1. The LogoutButton, somewhere deep in the component tree, will be responsible for calling the auth SDK and signing users out
  2. The GuardedPage component will listen and redirect the user out when it detects the user signs out
<GuardedPage whenSignedOut='/sign-in'>
<LogoutButton />
</GuardedPage>