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.
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.