Auth Configuration
Wire Supabase to the Zutra auth UI components — login, signup, forgot password, success page.
Overview
Zutra ships the auth UI (login, signup, forgot-password, success page) but leaves the backend open. The template is designed to work with Supabase as the identity provider — Supabase gives you OAuth (GitHub, Google, Discord), magic links, sessions, and Postgres in one project.
The four auth pages live at:
src/pages/login.astro
src/pages/signup.astro
src/pages/forgot-password.astro
src/pages/success.astro ← post-checkout landing
The form components live at:
src/components/auth/LoginForm.astro
src/components/auth/SignupForm.astro
src/components/auth/ForgotPasswordForm.astro
src/components/auth/SuccessReceipt.astro
The form components render correctly out of the box. Wiring them to Supabase is a matter of:
- Creating a Supabase project
- Adding env vars
- Replacing the placeholder submit handlers in each form with
supabase.auth.signInWithPassword()/signUp()/resetPasswordForEmail()calls
1. Create a Supabase project
- Sign up at supabase.com
- Click New project, choose a region close to your users
- Wait ~2 minutes for provisioning
- Go to Settings → API and copy:
- Project URL →
PUBLIC_SUPABASE_URL - anon / public key →
PUBLIC_SUPABASE_ANON_KEY
- Project URL →
2. Add environment variables
# .env
PUBLIC_SUPABASE_URL=https://your-project.supabase.co
PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...
Restart pnpm dev after editing .env — Vite needs to re-read the variables.
3. Install the Supabase client
pnpm add @supabase/supabase-js
Create src/lib/supabase.ts:
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
4. Enable providers in Supabase
In Supabase Dashboard → Authentication → Providers, toggle on whichever you want to support. Common ones:
- Email (magic link) — on by default
- GitHub — needs OAuth app credentials from github.com/settings/apps
- Google — needs OAuth credentials from Google Cloud Console
- Discord — needs OAuth app from discord.com/developers
For each OAuth provider, Supabase gives you the exact callback URL to paste into the provider’s settings — usually https://<project-ref>.supabase.co/auth/v1/callback.
5. Set redirect URLs
In Supabase → Authentication → URL Configuration, add your site’s URLs as allowed redirects:
http://localhost:4321/auth/callback ← for local dev
https://yourdomain.com/auth/callback ← for production
Then create src/pages/auth/callback.astro to handle the redirect — this is where Supabase sends the user back with the session token. A minimal handler:
---
import { supabase } from '../../lib/supabase';
const { error } = await supabase.auth.exchangeCodeForSession(
Astro.url.searchParams.get('code') ?? ''
);
return Astro.redirect(error ? '/login' : '/dashboard');
---
6. Wire the LoginForm component
Open src/components/auth/LoginForm.astro. Replace the submit handler with a Supabase call. The form already has email/password fields with the right names — you just need to make it submit:
import { supabase } from '../../lib/supabase';
const form = document.querySelector('#login-form') as HTMLFormElement;
form?.addEventListener('submit', async (e) => {
e.preventDefault();
const data = new FormData(form);
const { error } = await supabase.auth.signInWithPassword({
email: data.get('email') as string,
password: data.get('password') as string,
});
if (error) {
// show error inline
} else {
window.location.href = '/dashboard';
}
});
The same pattern applies to SignupForm (signUp), ForgotPasswordForm (resetPasswordForEmail), and SuccessReceipt (reads session/user from Supabase to render the receipt).
7. Add Row-Level Security
Always enable RLS on every Supabase table. Example policy:
alter table documents enable row level security;
create policy "Users can read own documents"
on documents for select
using (auth.uid() = user_id);
This is defense-in-depth — even if a query forgets the where user_id = auth.uid() clause, the database refuses the row.
8. Gate the dashboard
src/pages/dashboard.astro is currently public. To require auth, add a session check at the top:
---
import { supabase } from '../lib/supabase';
const { data: { session } } = await supabase.auth.getSession();
if (!session) return Astro.redirect('/login');
---
Same for src/pages/dashboard/*.astro sub-pages.
Production checklist
-
PUBLIC_SUPABASE_URLandPUBLIC_SUPABASE_ANON_KEYset in production env - Supabase redirect URLs include your production domain
- OAuth providers configured with production callback URLs
- RLS enabled on every table
- Session middleware / dashboard gate in place
Next steps
- Supabase Integration — schema design and RLS patterns
- Forms & API Routes — backend routes for waitlist and contact forms
- Environment Variables — full env reference