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:
- The user has logged in
- 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:
- The
LogoutButton
, somewhere deep in the component tree, will be responsible for calling the auth SDK and signing users out - The
GuardedPage
component will listen and redirect the user out when it detects the user signs out
<GuardedPage whenSignedOut='/sign-in'> <LogoutButton /></GuardedPage>