Skip to content
Zutra HyperSaaS — 6 languages, auth UI, dashboard, 69 tests
Get it on Gumroad

Zutra v1.3.0 — 6 languages, auth UI, dashboard, 69 tests. Saves 80+ hours.

See what's included

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:

  1. Creating a Supabase project
  2. Adding env vars
  3. Replacing the placeholder submit handlers in each form with supabase.auth.signInWithPassword() / signUp() / resetPasswordForEmail() calls

1. Create a Supabase project

  1. Sign up at supabase.com
  2. Click New project, choose a region close to your users
  3. Wait ~2 minutes for provisioning
  4. Go to Settings → API and copy:
    • Project URLPUBLIC_SUPABASE_URL
    • anon / public keyPUBLIC_SUPABASE_ANON_KEY

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_URL and PUBLIC_SUPABASE_ANON_KEY set 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