DEV Community

David J Song
David J Song

Posted on

NextAuth + Supabase: Handling Authentication in a Modern Full-Stack App

When I built ONNS (옷늘날씨), I wanted sign-in to feel effortless. Nobody wants to manage yet another password, and I definitely did not want to spend weeks reinventing authentication. That is why I combined NextAuth with Supabase. Together, they allowed me to focus on building the fun parts of the app while still maintaining solid security.


Why This Combo?

  • NextAuth is the de facto standard for authentication in Next.js apps. It supports OAuth providers, session management, JWTs, and integrates seamlessly with App Router.
  • Supabase provides a complete Postgres backend with real-time and storage. Since user accounts and posts live in Supabase, tying authentication into the same ecosystem just made sense.

By combining them, I got the best of both worlds: flexible provider-based login through NextAuth, and user records managed in Supabase.


The Flow

  1. A user clicks Sign in with Google (or another provider).
  2. NextAuth handles the OAuth handshake.
  3. On success, NextAuth calls a custom adapter that syncs the user with Supabase.
  4. The session is then available across the app.

This way, authentication feels native, and all user-related data stays consistent in the database.


Example Implementation

NextAuth config:

// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { SupabaseAdapter } from "@next-auth/supabase-adapter";

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  adapter: SupabaseAdapter({
    url: process.env.SUPABASE_URL!,
    secret: process.env.SUPABASE_SERVICE_ROLE_KEY!,
  }),
  secret: process.env.NEXTAUTH_SECRET,
};

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
Enter fullscreen mode Exit fullscreen mode

Protecting a route:

// app/feed/page.tsx
import { getServerSession } from "next-auth";
import { authOptions } from "../api/auth/[...nextauth]/route";

export default async function FeedPage() {
  const session = await getServerSession(authOptions);

  if (!session) {
    return <p>Please sign in to view the feed.</p>;
  }

  return <p>Welcome, {session.user?.name}! Here is your personalized feed.</p>;
}
Enter fullscreen mode Exit fullscreen mode

Benefits I Saw

Simplicity: I did not need to roll my own login system. NextAuth handled the heavy lifting.

Security: Supabase stored user records in Postgres, and I could enforce RLS (row-level security) policies.

Flexibility: Adding more providers (GitHub, Twitter) is almost copy-paste.

Integration: Since posts, comments, and profiles already lived in Supabase, user identities aligned perfectly with the data model.


A Quick Lesson

NextAuth and Supabase both support authentication separately. You could use Supabase Auth alone. However, by using NextAuth, I achieved tighter integration with Next.js features, such as middleware and server actions. The adapter pattern meant I did not have to pick one or the other—I could blend them.


Final Thought

Authentication is rarely the most exciting part of a project, but it is the part that can ruin the experience if it goes wrong. By leveraging NextAuth and Supabase, ONNS achieved smooth user authentication and a maintainable setup for me.


How do you usually handle authentication in your Next.js projects?

Top comments (0)