Authentication with NextJS and Supabase

August 2, 2021
views 7 min read

For this article, we'll be building a NextJS application and also implementing authentication and other resources with Supabase.

Let's get started ↓


To initialize our NextJS application we'll first run:

npx create-next-app --ts

In case you're wondering --ts means we'll be using Typescript.

If you see this:

Need to install the following packages:
  create-next-app
Ok to proceed? (y)

Press enter (↵)

You should then see a prompt to give your application a name, either use the default name (my-app) or pick whatever name you want.

? What is your project named? › my-app

Great. Open your application's folder and run npm run dev, we should have our NextJS ready to be used. Open your browser on localhost:3000, you should see something like the screenshot below.


TailwindCSS

We'll install TailwindCSS so we can have a fancier login screen, to do that run:

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

Once the packages have been installed run the following command to create the configuration files:

npx tailwindcss init -p

Lastly, open styles/globals.css and replace all code with:

@tailwind base;
@tailwind components;
@tailwind utilities;

If you want a more in-depth tutorial on how to use TailwindCSS with NextJS check this article.

Awesome, next we'll get rid of some boilerplate that create-next-app created for us.


Getting rid of boilerplate

First navigate to /pages/index.tsx and replace all that code with:

const Homepage: React.FC = () => {
  return <>Homepage</>
}

export default Homepage

If you like you can also delete the following:

  • pages/api (folder)
  • styles/Home.module.css (file)

Supabase

Let us take care of installing Supabase and build our auth page. First, you'll need to create a Supabase account, navigate to their page and click on Start your project, you'll need to use your GitHub account to login.

Click on New project and fill in the details. It should take a few minutes to setup everything, after that you'll be provided a unique URL of your project and a unique key as well.

Alright, we can go ahead and install Supabase:

npm install --save @supabase/supabase-js

I usually like to create a lib folder to keep these services all in one place, let's do that:

mkdir lib && touch lib/supabase.ts

Inside supabase.ts put the following code:

import { createClient } from '@supabase/supabase-js'

const supabaseUrl = '<YOUR_PROJECT_URL>'
const supabaseKey = '<YOUR_SUPABASE_KEY>'
const supabase = createClient(supabaseUrl, supabaseKey)

export default supabase

Make sure you don't leave your Supabase key visible before putting your code in any repository, it's good practice to use environment variables for these scenarios.

Alright, we should be able to connect to our Supabase project. Let's take care of building an auth page.


Authentication

To keep this simple we'll be using a passwordless authentication approach, in this case, we'll use Magic Link, Supabase provides it out of the box.

Run:

touch pages/auth.tsx

Inside auth.tsx let's put a simple component just to make sure it's working.

const Auth: React.FC = () => {
  return (
    <>
      Our auth page will be here!
    </>
  )
}

export default Auth

Change index.tsx and add a link to our new auth page:

import Link from 'next/link'

const Homepage: React.FC = () => {
  return (
    <>
      <Link href="/auth">Login now!</Link>
    </>
  )
}

export default Homepage

If you run your application you should be able to navigate from the homepage to our new auth page.

Let's add some magic to our auth page, you can replace all code with the following:

const Auth: React.FC = () => {
  return (
    <div className="border rounded-lg p-12 w-4/12 mx-auto my-48">
      <h3 className="font-extrabold text-3xl">Ahoy!</h3>

      <p className="text-gray-500 text-sm mt-4">
        Fill in your email, we'll send you a magic link.
      </p>

      <form>
        <input
          type="email"
          placeholder="Your email address"
          className="border w-full p-3 rounded-lg mt-4 focus:border-indigo-500"
        />

        <button
          type="submit"
          className="bg-indigo-500 text-white w-full p-3 rounded-lg mt-8 hover:bg-indigo-700"
        >
          Let's go!
        </button>
      </form>
    </div>
  )
}

export default Auth

We now have somewhat a fancy login component, should look like this:

Great, now we need to handle login on itself, using Supabase this is super easy, let's make some changes on our auth component. First of all, we'll need to import Supabase from /lib:

import supabase from '../lib/supabase'

const Auth: React.FC = () => {
  return (
    // ...
  )
}

export default Auth

We need a way of saving the email address, we can use React Hooks for what:

import { useState } from 'react'

import supabase from '../lib/supabase'

const Auth: React.FC = () => {
  const [email, setEmail] = useState<string>()

  return (
    // ...
  )
}

export default Auth

Great, we're just missing a tiny details, we need to trigger an onChange event on our input:

<input
  type="email"
  placeholder="Your email address"
  className="border w-full p-3 rounded-lg mt-4 focus:border-indigo-500"
  onChange={e => setEmail(e.target.value)}
/>

Now we need to handle when the form is submitted:

// ...

const Auth: React.FC = () => {
  return (
    <div className="border rounded-lg p-12 w-4/12 mx-auto my-48">
      <h3 className="font-extrabold text-3xl">Ahoy!</h3>

      <p className="text-gray-500 text-sm mt-4">
        Fill in your email, we'll send you a magic link.
      </p>

      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="Your email address"
          className="border w-full p-3 rounded-lg mt-4 focus:border-indigo-500"
        />

        <button
          type="submit"
          className="bg-indigo-500 text-white w-full p-3 rounded-lg mt-8 hover:bg-indigo-700"
        >
          Let's go!
        </button>
      </form>
    </div>
  )
}

export default Auth

Awesome, let's create the handleSubmit function and debug it a little bit, see if it's receiving what we're expecting:

// ...

const Auth: React.FC = () => {
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()

    const { error } = await supabase.auth.signIn({ email })

    console.log({ error })
  }

  return (
    // ...
  )
}

export default Auth

If the error is null it means everything worked, if you check your email address you have an email from Supabase, clicking the link will authenticate you, we'll make sure you're authenticated by fetching your user and debugging it.


The user

First, let's change the auth component to redirect to the homepage in case the authentication worked:

import { useRouter } from 'next/router'

const Auth: React.FC = () => {
  const [email, setEmail] = useState<string>()
  const { push } = useRouter()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()

    const { error } = await supabase.auth.signIn({ email })

    if (!error) push('/')
  }

  return (
    // ...
  )
}

export default Auth

And a few changes to fetch our user on the homepage component:

import Link from 'next/link'

import supabase from '../lib/supabase'

const Homepage: React.FC = () => {
  const user = supabase.auth.user()

  return (
    <>
      <Link href="/auth">Login now!</Link>

      <pre>
        <code>
          {JSON.stringify(user, null, 2)}
        </code>
      </pre>
    </>
  )
}

export default Homepage

Now go ahead, login with your email address, check your inbox and click the link. When your application opens you should see an object of your authenticated user, something similar with:

{
  "id": "59b05yef-1213-4c53-8ef9-8eb4b50d1u75",
  "aud": "authenticated",
  "role": "authenticated",
  "email": "telmo@hey.com",
  "email_confirmed_at": "2021-07-30T09:50:09.219595Z",
  "phone": "",
  "confirmation_sent_at": "2021-07-30T09:43:26.799986Z",
  "confirmed_at": "2021-07-30T09:50:09.219595Z",
  "recovery_sent_at": "2021-08-02T07:36:19.879576Z",
  "last_sign_in_at": "2021-08-02T07:36:31.757178Z",
  "app_metadata": {
    "provider": "email"
  },
  "user_metadata": {},
  "created_at": "2021-07-30T09:43:26.796244Z",
  "updated_at": "2021-07-30T09:43:26.796244Z"
}

Voilà! You can find the full code on this repository.

Read next
Small Javascript & React tricks I've been using
March 3, 2021
views