• Blog
  • Documentation
  • Courses
  • Changelog
  • AI Starters
  • UI Kit
  • FAQ
  • Supamode
    New
  • Pricing

Launch your next SaaS in record time with Makerkit, a React SaaS Boilerplate for Next.js and Supabase.

Makerkit is a product of Makerkit Pte Ltd (registered in the Republic of Singapore)Company Registration No: 202407149CFor support or inquiries, please contact us

About
  • FAQ
  • Contact
  • Verify your Discord
  • Consultation
  • Open Source
  • Become an Affiliate
Product
  • Documentation
  • Blog
  • Changelog
  • UI Blocks
  • Figma UI Kit
  • AI SaaS Starters
License
  • Activate License
  • Upgrade License
  • Invite Member
Legal
  • Terms of License

Email Link Authentication with Firebase and Next.js

Sep 2, 2022

Learn how to add Email Link authentication to your SaaS application with Firebase Auth and Next.js

firebase
auth

While being a less-known authentication method, Email Links are a great way to allow users to sign in to your application. It's easy and simple to use, and Firebase offers full support for it.

In this post, we will learn how to implement Email Link authentication in any Next.js application with Firebase Auth.

In this post, we assume you already have a bare-bone Next.js codebase with reactfire installed and the basic Firebase setup.

Some of the code below contains references to components and utilities contained in the Makerkit SaaS Boilerplate

Creating a Form to send an Email Link

First, we want to create a form in our authentication pages that can read an email address and generate an email link.

Here is what we will do:

  1. Create a simple form that takes an email address
  2. When submitted, we generate the link using the sendSignInLinkToEmail function from firebase/auth, and save the email in localStorage
EmailLinkAuth.tsx
const EmailLinkAuth: React.FC = () => {
const auth = useAuth();
const { state, setLoading, setData, setError } = useRequestState<void>();
const onSubmit: FormEventHandler<HTMLFormElement> = useCallback(
async (event) => {
event.preventDefault();
// read the email field of the form
const target = event.currentTarget;
const data = new FormData(target);
const email = data.get('email') as string;
setLoading(true);
// set up return URL (where we will redirect the user)
const settings = {
url: getAuthUrl(),
handleCodeInApp: true,
};
try {
// send sign in link
await sendSignInLinkToEmail(
auth,
email,
settings
);
// save email in storage, so we can compare
// it when the user uses the link from the email
storeEmailInStorage(email);
// success (turns state.success to "true")
setData();
} catch (error) {
setError(error);
}
},
[auth, setData, setError, setLoading]
);
{ /* SUCCESS! */ }
if (state.success) {
return (
<span>
Yay, link successfully sent!
</span>
);
}
return (
<form className={'w-full'} onSubmit={onSubmit}>
<div className={'flex flex-col space-y-2'}>
<TextField>
<TextField.Label>
Email Address
<TextField.Input
data-cy={'email-input'}
required
type="email"
placeholder={'your@email.com'}
name={'email'}
/>
</TextField.Label>
</TextField>
<Button loading={state.loading}>
<If
condition={state.loading}
fallback={<>Send Email Link</>}
>
Sending Email Link...
</If>
</Button>
</div>
<If condition={state.error}>
<span>
Sorry, we encountered an error
</span>
</If>
</form>
);
};
function getAuthUrl() {
const origin = window.location.origin;
const path = '/auth/link';
return [origin, path].join('');
}
function storeEmailInStorage(email: string) {
window.localStorage.setItem('emailForSignIn', email);
}
export default EmailLinkAuth;

When using the Firebase Auth emulator, the link will be printed to the console that is running the emulators.

Now, we have to create the /auth/link page component, in which we will verify the code and, if successful, sign the user in.

Creating the Email Link page component

As said before, we create this page at /auth/link.

Before being able to sign the user in, we're going to do a couple of checks:

  1. first, we use the Firebase function isSignInWithEmailLink to verify we're using the Email Link authentication method
  2. secondly, we verify we're able to retrieve the email address used to receive the link
  3. then, we sign the user in using the function signInWithEmailLink
  4. if successful, we delete the email from local storage and redirect the user to the application
  5. whenever we cannot sign the user in, we display the users an error
tsx
const EmailLinkAuthPage: React.FC = () => {
const auth = useAuth();
const router = useRouter();
const requestExecutedRef = useRef<boolean>();
const { state, setError } = useRequestState<void>();
const redirectToAppHome = useCallback(() => {
return router.push(appHome);
}, [router]);
// in this effect, we execute the functions to log the user in
useEffect(() => {
// let's prevent duplicate calls (which should only happen in dev mode)
if (requestExecutedRef.current) {
return;
}
const href = getOriginHref();
// do not run on the server
if (!href) {
setError('generic');
return;
}
// let's verify the auth method is email link
if (!isSignInWithEmailLink(auth, href)) {
setError('generic');
return;
}
const email = getStorageEmail();
// let's get email used to get the link
if (!email) {
setError('generic');
return;
}
void (async () => {
requestExecutedRef.current = true;
try {
// sign in with link, and retrieve the ID Token
await signInWithEmailLink(auth, email, href);
// let's clear the email from the storage
clearEmailFromStorage();
// redirect user to the home page
await redirectToAppHome();
} catch (e) {
if (e instanceof FirebaseError) {
setError(e.code);
} else {
setError('generic');
}
}
})();
}, [
auth,
loading,
redirectToAppHome,
setError,
]);
return (
<Layout>
<If condition={loading}>
Signing in...
</If>
<If condition={state.error}>
<div className={'flex flex-col space-y-2'}>
{ state.error }
</div>
</If>
</Layout>
);
};
function getStorageEmail() {
if (!isBrowser()) {
return;
}
return window.localStorage.getItem(EMAIL_LOCAL_STORAGE_KEY);
}
function clearEmailFromStorage() {
window.localStorage.removeItem(EMAIL_LOCAL_STORAGE_KEY);
}
function getOriginHref() {
if (!isBrowser()) {
return;
}
return window.location.href;
}
export default EmailLinkAuthPage;

🎉 And that's it! This is all you need to create a sound email authentication flow with Firebase Auth and Next.js.

Some other posts you might like...
Dec 18, 2022Programmatic Authentication with Supabase and CypressTesting code that requires users to be signed in can be tricky. In this post, we show you how to sign in programmatically with Supabase Authentication to improve the speed of your Cypress tests and increase their reliability.
Dec 17, 2022How to reduce and boost your Firebase cold start timesFirebase cold start times are a common problem for developers. In this tutorial, we'll show you how to reduce and boost your Firebase cold start times.
Dec 6, 2022Authenticating users with Remix and SupabaseLearn how to use Remix and Supabase to authenticate users in your application.
Oct 21, 2022Counting a collection's documents with Firebase FirestoreIn this article, we learn how to count the number of documents in a Firestore collection using a custom React.js hook.
Oct 21, 2022Pagination with React.js and Firebase FirestoreIn this article, we learn how to paginate data fetched from Firebase Firestore with React.js
Oct 6, 2022Limiting the Firebase Storage space used by each customerLimiting the amount of Storage space used by your customers can be tricky. In this article, we show how to set a quota for each of your customers.