Authentication
Supabase Auth integration with server-side session management.
Overview
Supabase Auth manages identity and sessions. Native Start request middleware preserves refreshed cookies, and the dashboard verifies the user before rendering protected pages.
Supported Methods
- Email/Password with verification
- Magic links (OTP)
- OAuth (Google and GitHub)
Architecture
How It Works
The user submits a form to the existing same-origin /api/auth/* URL. The
Start API adapter passes the request to Hono, the auth handler calls the auth
service, and the service uses the cookie-backed Supabase server client. The
request middleware asks Supabase to verify the current token with
getClaims()
while preserving any refreshed session cookies.
Password forms keep provider and validation failures on the current form. After
a successful sign-in, sign-up, password setup, or sign-out, the shared auth
transition helper clears cached session and user-status results before the next
route renders.
OAuth, magic-link, and email-confirmation redirects return to
/auth/callback. That request-bound Route Handler exchanges Supabase's
single-use PKCE code and writes the cookie session before sending the user to
/dashboard. Callback failures return to sign-in with generic application copy;
provider error descriptions and authorization codes are never displayed.
Auth Service
The server-only auth service (src/services/auth/service.ts) owns Supabase Auth
calls and their redirect results. Password routes preserve the supplied password;
only email addresses are trimmed at the Hono boundary.
| Password result | Destination |
|---|---|
| Sign-up creates a session immediately | /pricing with success status |
| Sign-up requires email confirmation | / with confirmation status |
| Password sign-in succeeds | /dashboard with success status |
| Provider or validation fails | Current auth form with error detail |
New and changed passwords require at least eight characters. Keep password sign-in opaque: do not trim it or apply the creation minimum to existing users.
The API reference lists the exact response shapes. Keep the service's validation, existing-account, and provider-error branches when customizing it.
API Routes
The Hono auth module trims the email, preserves the password, calls the auth service, and returns its JSON result with a success or failure status.
The sign-up handler uses the shared request validator before calling its service:
import { Hono } from "hono";
import { validateJson } from "@/api/validation";
import { signUpSchema } from "@/services/auth/schemas";
import { signUp } from "@/services/auth";
export const authRoutes = new Hono();
authRoutes.post("/sign-up", validateJson(signUpSchema), async (c) => {
const result = await signUp(c.req.valid("json"));
return c.json(result, result.ok ? 200 : 400);
});Malformed JSON and invalid fields return the shared 400 invalid_request
response before the service runs. Keep that validation boundary when extending
an authentication endpoint.
Client-side password transitions
Use the shipped mutation hooks rather than calling Supabase from browser components. After a successful password transition, clear session-dependent query results before navigating:
const queryClient = useQueryClient();
const signUpMutation = useSignUpMutation();
const result = await signUpMutation.mutateAsync({ email, password });
if (result.ok) {
reconcileAuthTransition(queryClient);
router.clearCache();
await router.navigate({ href: result.redirectPath });
router.clearCache();
}Session Management
Request session refresh
src/start.ts calls the session helper in src/lib/supabase/middleware.ts
for matched page requests. The Supabase factory uses native request/response
cookies, preserving every refreshed chunk and cache header through server
rendering, raw Markdown and redirects. Hono routes use the same factory.
Getting the Current User
Inside server functions, services or API handlers:
import { createSupabaseServerClient } from "@/lib/supabase/server";
async function readCurrentUser() {
const supabase = await createSupabaseServerClient();
const { data: { user } } = await supabase.auth.getUser();
return user;
}In client components, use the session hook:
import { useSession } from "@/hooks/auth";
const session = useSession();
const user = session?.user ?? null;Protected Routes
src/routes/dashboard/route.tsx loads the server bootstrap in beforeLoad,
before its shell and nested pages render. The admission check in
src/lib/dashboard.server.ts validates the current user:
getDashboardBootstrap() calls supabase.auth.getUser() inside the active
server request and throws redirect({ to: '/signin' }) when the provider
returns an error or no user. It then loads schema readiness, locale, and the
safe user projection. Keep request-bound cookie reads inside that function
or an active server-function handler; module initialization has no request.
Keep client status queries for account and billing data, not for route admission. An unauthenticated request redirects before protected HTML is sent.
OAuth Setup
Enable Provider in Supabase
Go to Supabase Dashboard → Authentication → Providers
Configure OAuth App
Create an OAuth client in the Google developer console and get credentials. Google is the default OAuth option in the sign-in and sign-up forms. To expose GitHub sign-in, configure that provider and add a GitHub control to both forms using the existing OAuth handler.
Add Credentials to Supabase
Enter Client ID and Secret in Supabase provider settings
Set Redirect URLs
Add https://<project-ref>.supabase.co/auth/v1/callback to the OAuth provider's
allowed redirects. This is the provider callback, not your app's hosted URL.
Configure the Hosted App URL
After your hosted domain is assigned, open Supabase URL Configuration. Set Site URL to https://<your-domain>, add https://<your-domain>/auth/callback and https://<your-domain>/auth/reset-password to Redirect URLs, and save before testing OAuth, confirmation links, magic links, or recovery.
Set the Hosted Password Minimum
In your Supabase project's Auth settings, set the minimum password length
to 8. The template and local Supabase configuration already enforce the same
boundary, but the hosted setting is what prevents a direct Auth API call from
bypassing it.
Password recovery
Recovery and guest password-setup links return to /auth/reset-password. The
page validates the Supabase recovery session, requires an eight-character
password, and then continues to Dashboard. The callback route also redirects
older, still-valid fragment recovery links to this page during the route
transition.
Test Google in the deployed Production environment. If you enable GitHub, test it separately because each provider has independent credentials and callback configuration. Local tests prove application routing and recovery compatibility; they do not prove a hosted provider configuration.
Database Trigger
New users are automatically added to the users table via a database trigger:
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS trigger AS $$
BEGIN
INSERT INTO public.users (id, email)
VALUES (new.id, new.email);
RETURN new;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user();Related
- Payments - Connect auth to billing
- Environment Variables - Auth configuration
- API Routes - All auth endpoints
Was this page helpful?