/ astro-integrations / How to Integrate Supabase Auth with Astro: Complete Guide
astro-integrations 5 min read

How to Integrate Supabase Auth with Astro: Complete Guide

Step-by-step guide to integrating Supabase Auth with your Astro website. Setup, configuration, and best practices.

How to Integrate Supabase Auth with Astro: Complete Guide

Supabase Auth provides a full authentication system built on top of PostgreSQL with support for email/password, magic links, social OAuth providers, and phone-based authentication. Since Supabase also serves as your database, you get authentication and data access from a single service. Pairing Supabase Auth with Astro's server-side rendering gives you secure session management and protected routes without third-party session stores.

This guide covers setting up Supabase Auth in an Astro project with server-side session handling using cookies.

Prerequisites

Before starting, make sure you have:

  • Node.js 18+ installed
  • An existing Astro project (v3.0+ with SSR enabled)
  • A Supabase project created at supabase.com
  • Your Supabase project URL and anon key (found in Settings > API)
  • Basic familiarity with cookies and session management

Installation

Install the Supabase JavaScript client and the SSR helper package:

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

The @supabase/ssr package provides cookie-based session management designed for server-rendered frameworks like Astro.

Add an SSR adapter if you have not already:

npx astro add node

Configuration

Environment Variables

Add your Supabase credentials to .env:

PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
PUBLIC_SUPABASE_ANON_KEY=your_anon_key_here

The PUBLIC_ prefix makes these available in both server and client contexts in Astro.

Creating the Supabase Client

Create a server-side Supabase client that manages sessions through cookies:

// src/lib/supabase.ts
import { createServerClient, parseCookieHeader } from '@supabase/ssr';
import type { AstroCookies } from 'astro';

export function createSupabaseClient(request: Request, cookies: AstroCookies) {
  return createServerClient(
    import.meta.env.PUBLIC_SUPABASE_URL,
    import.meta.env.PUBLIC_SUPABASE_ANON_KEY,
    {
      cookies: {
        getAll() {
          return parseCookieHeader(request.headers.get('cookie') ?? '');
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) => {
            cookies.set(name, value, options);
          });
        },
      },
    }
  );
}

Astro Config

Configure hybrid or server mode:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'server',
  adapter: node({ mode: 'standalone' }),
});

Common Patterns

Authentication Middleware

Create middleware to make the user session available on every request:

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { createSupabaseClient } from './lib/supabase';

export const onRequest = defineMiddleware(async ({ request, cookies, locals }, next) => {
  const supabase = createSupabaseClient(request, cookies);

  const { data: { user } } = await supabase.auth.getUser();
  locals.user = user;
  locals.supabase = supabase;

  return next();
});

This gives every page and API route access to Astro.locals.user and Astro.locals.supabase.

Sign Up and Login Pages

Create a login page with email/password authentication:

---
// src/pages/login.astro
if (Astro.locals.user) {
  return Astro.redirect('/dashboard');
}
---

<html>
  <body>
    <h1>Log In</h1>
    <form method="POST" action="/api/auth/login">
      <label>Email <input type="email" name="email" required /></label>
      <label>Password <input type="password" name="password" required /></label>
      <button type="submit">Log In</button>
    </form>
    <p>No account? <a href="/signup">Sign up</a></p>
  </body>
</html>

Login API Route

// src/pages/api/auth/login.ts
import type { APIRoute } from 'astro';
import { createSupabaseClient } from '../../../lib/supabase';

export const POST: APIRoute = async ({ request, cookies, redirect }) => {
  const formData = await request.formData();
  const email = formData.get('email') as string;
  const password = formData.get('password') as string;

  const supabase = createSupabaseClient(request, cookies);

  const { error } = await supabase.auth.signInWithPassword({
    email,
    password,
  });

  if (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 400,
    });
  }

  return redirect('/dashboard');
};

Sign Up API Route

// src/pages/api/auth/signup.ts
import type { APIRoute } from 'astro';
import { createSupabaseClient } from '../../../lib/supabase';

export const POST: APIRoute = async ({ request, cookies, redirect }) => {
  const formData = await request.formData();
  const email = formData.get('email') as string;
  const password = formData.get('password') as string;

  const supabase = createSupabaseClient(request, cookies);

  const { error } = await supabase.auth.signUp({
    email,
    password,
  });

  if (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 400,
    });
  }

  return redirect('/check-email');
};

Protecting Pages

---
// src/pages/dashboard.astro
const user = Astro.locals.user;

if (!user) {
  return Astro.redirect('/login');
}
---

<html>
  <body>
    <h1>Dashboard</h1>
    <p>Logged in as {user.email}</p>
    <form method="POST" action="/api/auth/logout">
      <button type="submit">Log Out</button>
    </form>
  </body>
</html>

Logout Route

// src/pages/api/auth/logout.ts
import type { APIRoute } from 'astro';
import { createSupabaseClient } from '../../../lib/supabase';

export const POST: APIRoute = async ({ request, cookies, redirect }) => {
  const supabase = createSupabaseClient(request, cookies);
  await supabase.auth.signOut();
  return redirect('/login');
};

Troubleshooting

User is null even after login: The @supabase/ssr package relies on cookies being set correctly. Make sure your setAll function in the client factory actually calls cookies.set() for each cookie. Also verify you are using getUser() (which validates the token server-side) and not getSession() (which only reads the local token).

Cookies not persisting across requests: Check that your Astro adapter supports cookie manipulation. The Node.js adapter works by default. If deploying to Cloudflare or Vercel, use their respective adapters.

OAuth redirects failing: When using social login providers, configure the redirect URL in both Supabase (Authentication > URL Configuration) and the OAuth provider's dashboard. The callback URL should be https://your-project.supabase.co/auth/v1/callback.

"Invalid Refresh Token" errors: This happens when the refresh token stored in cookies has expired or been revoked. Clear the user's cookies and ask them to log in again. Set reasonable token expiry times in Supabase Authentication settings.

Conclusion

Supabase Auth gives Astro projects a complete authentication system backed by PostgreSQL. The cookie-based SSR approach keeps sessions secure and server-validated, while the middleware pattern makes user data available across your entire application. Since Supabase also serves as your database, you can write Row Level Security policies that automatically restrict data access based on the authenticated user, creating a tight security model from authentication through to data access.