Magic Link Authentication and Route Controls with Supabase and Next.js
While Supabase is widely known for their real-time database and API layer, one of the things I like about it is the number of easy to set up authentication mechanisms it offers out of the box.
Magic Link
One of my favorites is Magic Link. You’ve probably used magic link in the past. Magic link sends a link to the user via email containing a link to authenticate with the service via a custom URL and access token.
When the user visits the URL, a session is set in their browser storage and the user is redirected back to the app, authenticating the user in the process.
This is becoming a very popular way to authenticate users as they do not have to keep up with another password, it provides a really great user experience.
Next.js
With Next.js, you have the ability to not only protect routes with client-side authorization, but for added security you can do server-side authorization and redirects in getServerSideProps
if a cookie has been set and is available in the request context.
This is also where Supabase comes in handy. There is built-in functionality for setting and getting the cookie for the signed in user in SSR and API routes:
Setting the user in an API route
Getting the user in an SSR or API route
Server-side redirects are typically preferred over client-side redirects from an SEO perspective — it’s harder for search engines to understand how client-side redirects should be treated.
You are also able to access the user profile from an API route using the getUserByCookie
function, opening up an entirely new set of use cases and functionality.
With Next.js and Supabase you can easily implement a wide variety of applications using this combination of SSG, SSR, and client-side data fetching and user authorization, making the combination (and any framework that offers this combination of capabilities) extremely useful and powerful.
What we’ll be building
In this post, we’ll build out a Next.js app that enables navigation, authentication, authorization, redirects (client and server-side), and a profile view.
The project that we’ll be building is a great starting point for any application that needs to deal with user identity, and is a good way to understand how user identity works and flows throughout all of the different places in a project using a modern hybrid framework like Next.js.
The final code for this project is located here
Building the app
To get started, you first need to create a Supabase account and project.
To do so, head over to Supabase.io and click Start Your Project. Authenticate with GitHub and then create a new project under the organization that is provided to you in your account.
Give the project a Name and Password and click Create new project.
It will take approximately 2 minutes for your project to be created.
Next, open your terminal and create a new Next.js app:
npx create-next-app supabase-next-authcd supabase-next-auth
The only dependency we’ll need is the @supabase/supabase-js
package:
npm install @supabase/supabase-js
Configuring the Supabase credentials
Now that the Next.js app is created, it needs a to know about the Supabase project in order to interact with it.
The best way to do this is using environment variables. Next.js allows environment variables to be set by creating a file called .env.local in the root of the project and storing them there.
In order to expose a variable to the browser you have to prefix the variable with NEXT_PUBLIC_.
Create a file called .env.local at the root of the project, and add the following configuration:
NEXT_PUBLIC_SUPABASE_URL=https://app-id.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-public-api-key
You can find the values of your API URL and API Key in the Supabase dashboard settings:
Creating the Supabase client
Now that the environment variables have been set, we can create a Supabase instance that can be imported whenever we need it.
Create a file named client.js in the root of the project with the following code:
Updating the index page
Next, let’s update pages/index.js to be something more simple than what is provided out of the box. This is just meant to serve as a basic landing page
Creating the sign in screen
Next, let’s create the Sign In screen. This will serve a form input for the user to provide their email address.
When the user submits the form, they will receive a magic link to sign in. This will work for both new as well as existing users!
Create a new file in the pages directory named sign-in.js:
The main thing in this file is this line of code:
By only providing the email address of the user, magic link authentication will happen automatically.
Profile view
Next, let’s create the profile view. Create a new file in the pages directory named profile.js:
To check for the currently signed in user we call supabase.auth.user()
.
If the user is signed in, we set the user information using the setProfile
function set up using the useState
hook.
If the user is not signed in, we client-side redirect using the useRouter
hook.
API Route
In pages/_app.js we’ll be needing to call a function to set the cookie for retrieval later in the SSR route.
Let’s go ahead and create that API route and function. This will be calling the setAuthCookie
API given to us by the Supabase client.
Create a new file named auth.js in the pages/api folder and add the following code:
Nav, auth listener, and setting the session cookie
The largest chunk of code we’ll need to write will be in pages/app.js. Here are the things we need to implement here:
- Navigation
- A listener to fire when authentication state changes (provided by Supabase)
- A function that will set the cookie with the user session
In addition to this, we’ll also need to keep up with the authenticated state of the user. We do this so we can toggle links, showing or hiding certain links based on if the user is or isn’t signed in.
We’ll demonstrate this here by only showing the Sign In link to users who are not signed in, and hiding it when they are.
The last page we need to implement is the route that will demonstrate server-side protection and redirects.
Since we have already implemented setting the cookie, we should now be able to read the cookie on the server if the user is signed in.
Like I mentioned previously, we can do this with the getUserByCookie
function.
Create a new file in the pages directory named protected.js and add the following code:
Testing it out
Now the app is built and we can test it out!
To run the app, open your terminal and run the following command:
npm run dev
When the app loads, you should be able to sign up, and sign in using the magic link. Once signed in, you should be able to view the profile page and see your user id as well as your email address.
Setting metadata and attributes
If you want to continue building out the user’s profile, you can do so easily using the update
method.
For example, let’s say we wanted to allow the user’s to set their location. We can do so with the following code:
const { user, error } = await supabase.auth.update({
data: {
city: "New York"
}
})
Now, when we fetch the user’s data, we should be able to view their metadata:
The final code for this project is located here