
Integrating Supabase Authentication with Astro: A Complete Session Management Tutorial
Astro has evolved from a static site generator into a powerful server-side rendering (SSR) framework. When building dynamic web applications with Astro, handling authentication securely is essential. While storing user sessions in browser LocalStorage is standard for single-page apps, SSR architectures require a server-friendly approach.
The most secure strategy is utilizing Supabase Authentication with HTTP-only cookies. This prevents Cross-Site Scripting (XSS) attacks while allowing Astro servers to verify session states before rendering pages.
In this tutorial, we will set up Astro in SSR mode, initialize Supabase Auth, build login/logout API endpoints, and implement an Astro middleware guard to secure private routes.
Enabling SSR Mode in Astro
To handle HTTP-only cookies and dynamic route verification, your Astro project must run in server-side rendering mode.
Update your astro.config.mjs to enable server-side output:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone',
}),
});Creating the Supabase Server Client
Since we are executing queries on the server, we need a helper to initialize the Supabase client. This helper will read and write session tokens dynamically from the incoming request cookies.
First, install the required packages:
pnpm add @supabase/supabase-jsNext, create a utility file called supabase.ts to construct the server-side client:
// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = import.meta.env.SUPABASE_URL;
const supabaseAnonKey = import.meta.env.SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
flowType: 'pkce',
persistSession: false,
detectSessionInUrl: false,
},
});By setting persistSession to false, we prevent the client from using local storage, forcing it to rely entirely on request cookies.
Building Login and Logout API Routes
To manage authentication tokens securely, we will create server endpoints that set and clear HTTP-only cookies.
The Login Endpoint
Create a file at src/pages/api/auth/signin.ts to handle login submissions:
// src/pages/api/auth/signin.ts
import type { APIRoute } from 'astro';
import { supabase } from '../../../lib/supabase';
export const POST: APIRoute = async ({ request, cookies, redirect }) => {
const formData = await request.formData();
const email = formData.get('email')?.toString();
const password = formData.get('password')?.toString();
if (!email || !password) {
return new Response('Email and password are required', { status: 400 });
}
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return new Response(error.message, { status: 400 });
}
const { access_token, refresh_token } = data.session;
// Set the access token cookie
cookies.set('sb-access-token', access_token, {
path: '/',
secure: true,
httpOnly: true,
sameSite: 'strict',
});
// Set the refresh token cookie
cookies.set('sb-refresh-token', refresh_token, {
path: '/',
secure: true,
httpOnly: true,
sameSite: 'strict',
});
return redirect('/dashboard');
};The Logout Endpoint
Create a file at src/pages/api/auth/signout.ts to clear the cookies on logout:
// src/pages/api/auth/signout.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ cookies, redirect }) => {
// Clear the authentication cookies
cookies.delete('sb-access-token', { path: '/' });
cookies.delete('sb-refresh-token', { path: '/' });
return redirect('/signin');
};Securing Private Routes using Middleware
Instead of writing session verification checks on every individual page, Astro provides a centralized middleware system to intercept requests and enforce access control.
Create a file called middleware.ts in your src directory:
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { supabase } from './lib/supabase';
export const onRequest = defineMiddleware(async (context, next) => {
const { url, cookies, redirect } = context;
const isDashboardRoute = url.pathname.startsWith('/dashboard');
if (isDashboardRoute) {
const accessToken = cookies.get('sb-access-token')?.value;
const refreshToken = cookies.get('sb-refresh-token')?.value;
if (!accessToken || !refreshToken) {
return redirect('/signin');
}
// Set the session context in the Supabase client
const { data, error } = await supabase.auth.setSession({
access_token: accessToken,
refresh_token: refreshToken,
});
if (error) {
// Clear invalid cookies
cookies.delete('sb-access-token', { path: '/' });
cookies.delete('sb-refresh-token', { path: '/' });
return redirect('/signin');
}
// Attach user details to the context local variables
context.locals.user = data.user;
}
return next();
});Once defined, this middleware intercepts every request targeting the /dashboard directory. If session cookies are missing or invalid, it redirects the browser immediately, ensuring unauthenticated requests never hit your Server Components.
Conclusion
Combining Supabase Auth with Astro SSR provides a robust, production-ready identity solution. Storing session tokens in secure, HTTP-only cookies removes XSS vulnerability risks. Moving the session check to middleware ensures that your routes are protected globally, keeping your individual page logic clean and focused on rendering user UI.