Authentication System using Supabase SDK

Project Setup

Before starting the project we need to get Supabse anon_key and project_url.

To get that.

  • Go to Project Setting
  • Go to API in configuration tab
  • Then copy the anon_key and project url

Now let's setup the nextjs Project

npx create-next-app@latest .

Use the default template. And make a .env file in root folder and paste the copied url

NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL

NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY

Then install the supabase SDK using following command.

npm install @supabase/supabase-js @supabase/ssr

Now create a utils folder in root of the project, inside utils create another folder supabase then create a client.ts and server.ts file

Inside client file.

/utils/supabase/client.ts

import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  // Create a supabase client on the browser with project's credentials
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}
/utils/supabase/server.ts

import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()


  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll()
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            )
          } catch {
            // The `setAll` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
      },
    }
  )
}

Now Setup Next Middleware

Since Server Components can't write cookies, you need middleware to refresh expired Auth tokens and store them.

Create a middleware.ts file in root of the folder, and middleware.ts file in the utils/supabase folder.

/middleware.ts

import { type NextRequest } from 'next/server'
import { updateSession } from '@/utils/supabase/middleware'

export async function middleware(request: NextRequest) {

    return await updateSession(request)
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
}
utils/supabase/middleware.ts

import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function updateSession(request: NextRequest) {
  let supabaseResponse = NextResponse.next({
    request,
  })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll()
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
          supabaseResponse = NextResponse.next({
            request,
          })
          cookiesToSet.forEach(({ name, value, options }) =>
            supabaseResponse.cookies.set(name, value, options)
          )
        },
      },
    }
  )

  // refreshing the auth token
  await supabase.auth.getUser()

  return supabaseResponse
}

Now Setup the actions Files

The actions file will contain the login and signup action. let's make...

Create a Folder in root named action then make a file inside the folder auth.ts

/action/auth.ts

'use server'

import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

import { createClient } from '@/utils/supabase/server'

export async function login(formData: {email: string, password: string}) {
  if(!formData.email || !formData.password) {
    throw new Error("FormData is Required")
  }
  const supabase = await createClient()

  const data = {
    email: formData.email,
    password: formData.password
  }

  const { error } = await supabase.auth.signInWithPassword(data)

  if (error) {
    redirect('/error')
  }

  revalidatePath('/', 'layout')
  redirect('/')
}

export async function signup(formData: {email: string, password: string, username: string}) {
  if(!formData.email || !formData.password || !formData.username) {
    throw new Error("FormData is Required")
  }
  const supabase = await createClient()
  
  const clientData = {
    email: formData.email,
    password: formData.password,
  }

  const { error } = await supabase.auth.signUp(clientData)

  if (error) {
    redirect('/error')
  }

  revalidatePath('/', 'layout')
  redirect('/login')
}

export async function logout() {
  const supabase = await createClient()

  await supabase.auth.signOut()

  revalidatePath('/', 'layout')
  redirect('/login')
}

Now make a error.tsx page inside action folder.

export default function ErrorPage() {
    return <p>Sorry, something went wrong</p>
}

Now Setup the login and signup page. Inside the app folder we will be using the shadcn to design the components and react-hook-form to handel the form.

/app/signup/page.tsx

"use client";
import { signup } from "@/action/auth";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import Link from "next/link";
import { useForm } from "react-hook-form";

export default function page() {
  const { register, handleSubmit } = useForm<{
    username: string;
    email: string;
    password: string;
  }>();

  return (
    <section className="flex items-center mt-10 justify-center">
      <form
        onSubmit={handleSubmit((data: { email: string; password: string; username: string }) =>
          signup(data)
        )}
        className="flex flex-col border border-black rounded-md p-4 gap-4"
      >
        <h1 className="text-2xl font-bold">Create your account</h1>
        <div>
          <Input {...register("username")} placeholder="Username" />
        </div>
        <div>
          <Input {...register("email")} placeholder="Email" />
        </div>
        <div>
          <Input {...register("password")} placeholder="Password" />
        </div>
          <Button>Signup</Button>
        <div>
          <p>
            Already have an account{" "}
            <Link href={"/login"} className="text-blue-800">
              Login
            </Link>
          </p>
        </div>
      </form>
    </section>
  );
}
/app/login/page.tsx

"use client";
import { login } from "@/action/auth";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import Link from "next/link";
import { useForm } from "react-hook-form";

export default function page() {
  const { register, handleSubmit } = useForm<{
    email: string;
    password: string;
  }>();

  return (
    <section className="flex items-center mt-10 justify-center">
      <form
        onSubmit={handleSubmit((data: { email: string; password: string }) =>
          login(data)
        )}
        className="flex flex-col border border-black rounded-md p-4 gap-4"
      >
        <h1 className="text-2xl font-bold">Login to your account</h1>
        <div>
          <Input {...register("email")} placeholder="Email" />
        </div>
        <div>
          <Input {...register("password")} placeholder="Password" />
        </div>
          <Button>Login</Button>
        <div>
          <p>
            Don't have an account{" "}
            <Link href={"/signup"} className="text-blue-800">
              Signup
            </Link>
          </p>
        </div>
      </form>
    </section>
  );
}

That's it. run the npm run dev go to the http://localhost:3000/signup create a user with username, email and password then conform the email in side the gmail. then login to your account.

Thank You.