Decorative background gradient
Back to Blog
Next.js Supabase Auth SSRMiddleware Authentication Next.jsSupabase RLS

Next.js Supabase Auth SSR — Middleware, Server Actions & RLS Guide

Production-ready Supabase Auth in the Next.js App Router — middleware session refresh, Server Actions login, and Row Level Security done right.

Next.js Supabase Auth SSR — Middleware, Server Actions & RLS Guide

Supabase Auth is straightforward in a client-only app, but the Next.js App Router's mix of Server Components, Server Actions, and middleware means session handling has to be done correctly across multiple execution contexts — or you'll get intermittent "logged out" bugs that are painful to debug.

This guide sets up a complete, production-correct flow: middleware session refresh, server-side auth checks, Server Action-based login, and Row Level Security.

Step 1: Install and Configure the SSR-Aware Client

Use @supabase/ssr, not the plain @supabase/supabase-js client, for anything server-rendered.

bash
ts
ts

Two separate factories: one for Server Components/Actions/Route Handlers, one for Client Components.

Step 2: Refresh Sessions in Middleware

This is the step most tutorials skip, and it's the one that causes "why am I randomly logged out" bugs in production.

ts

Calling supabase.auth.getUser() inside middleware is what triggers Supabase to refresh an expiring access token and reissue cookies — skip this and Server Components downstream will eventually see expired sessions.

Step 3: Handle Login with a Server Action

Keep credentials off the client-side JS execution path entirely by submitting the form directly to a Server Action.

tsx
tsx

For OAuth (GitHub, Google), call signInWithOAuth from a Server Action and redirect to the returned URL:

ts

You'll also need a Route Handler at /auth/callback to exchange the OAuth code for a session:

ts

Step 4: Read the Authenticated User in Server Components

tsx

Always use getUser(), not getSession(), before making an authorization decision on the server — getUser() revalidates the token against Supabase rather than trusting whatever is in the cookie.

Step 5: Enforce Authorization with Row Level Security

A valid session tells you who the user is. It says nothing about what rows they're allowed to touch — that's RLS's job, enforced at the database level regardless of what your application code does.

sql

With these policies active, even if application code has a bug that queries projects without a user_id filter, Postgres itself will only ever return rows the authenticated user owns — the anon key alone is never enough to read someone else's data.

Key Takeaways

Session handling in the App Router requires a server client for SSR contexts, a browser client for Client Components, and — critically — middleware that calls getUser() on every request to refresh expiring tokens. Route logins through Server Actions to keep credentials off the client, always verify identity with getUser() rather than getSession() before authorizing anything server-side, and treat Row Level Security as the real authorization boundary, not a backup to your application code.

Frequently Asked Questions

Why do I need middleware for Supabase auth in the App Router?

Supabase's auth session lives in cookies that expire and need silent refresh. Middleware runs on every request before your route renders, so it's the correct place to call supabase.auth.getUser() and rewrite the session cookies — without it, Server Components can end up reading stale or expired tokens.

What's the difference between getSession() and getUser() on the server?

getSession() reads the session from cookies without verifying it against Supabase's auth server, which means a tampered or stale cookie could pass. getUser() sends the access token to Supabase's server to verify it is still valid, making it the safe choice anywhere you're about to make an authorization decision.

Does Supabase Auth work with Server Actions?

Yes. Create a server client inside the Server Action using the request's cookies, then call supabase.auth.signInWithPassword, signUp, or signOut directly — no client-side fetch or API route needed.

Is Row Level Security required if I already check auth in my app code?

Yes, treat RLS as mandatory, not optional. Application-level checks can be bypassed by a bug, a missed code path, or direct calls to the Supabase REST/PostgREST API using the anon key. RLS enforces authorization at the database layer regardless of what your app code does.

Working on something similar? Take a look at my services and case studies, or book a free call to talk about your idea.

Related Articles

Silent Tool Failures in AI Agents — Why Your Agent Lies About Success
Ai Agent ReliabilityTool Call FailuresLlm Error Handling+1 more

Silent Tool Failures in AI Agents — Why Your Agent Lies About Success

Most AI agent bugs never throw an error — the tool call "succeeds" with empty, truncated, or stale data and the agent confidently reports the task as done. Here's how to catch failures that don't look like failures.

September 22, 2026Read more →
AI Coding Agents Compared in 2026 — Claude Code vs Cursor vs GitHub Copilot
Claude Code Vs CursorAI Coding AgentsGithub Copilot

AI Coding Agents Compared in 2026 — Claude Code vs Cursor vs GitHub Copilot

Compare Claude Code, Cursor, and GitHub Copilot for real development work — agentic workflows, codebase understanding, pricing, and which fits your team.

September 18, 2026Read more →
Best Authentication Libraries for Next.js in 2026 — Clerk vs Auth.js vs Better Auth
Nextjs AuthenticationClerk Vs NextauthBetter Auth

Best Authentication Libraries for Next.js in 2026 — Clerk vs Auth.js vs Better Auth

Compare Clerk, Auth.js (NextAuth), and Better Auth for Next.js — setup effort, hosted UI, pricing, and which fits your app.

September 18, 2026Read more →
Best Error Monitoring & Analytics Tools for Next.js in 2026 — Sentry vs PostHog vs Vercel Analytics
Nextjs Error MonitoringSentry Vs PosthogWeb Analytics

Best Error Monitoring & Analytics Tools for Next.js in 2026 — Sentry vs PostHog vs Vercel Analytics

Compare Sentry, PostHog, and Vercel Analytics for a Next.js app — error tracking, product analytics, session replay, and pricing.

September 18, 2026Read more →
How to Add AI Features to an Existing Web App (Without a Rebuild)
Add AI To Existing AppAI SaaS FeaturesOpenAI API Integration

How to Add AI Features to an Existing Web App (Without a Rebuild)

A practical guide to adding real AI features, like matching, extraction, and automation, to a web app or SaaS product you already have, without rebuilding it from scratch.

September 16, 2026Read more →
AI Agent Observability and Tracing in Production
Ai Agent ObservabilityLlm TracingAgent Monitoring+2 more

AI Agent Observability and Tracing in Production

Why logging inputs and outputs isn't enough for AI agents in production — and how trace-level observability catches failures evals and logs miss.

September 14, 2026Read more →

Trending Topics