ShipFree uses Supabase for authentication, providing a secure and scalable solution with multiple authentication methods.
.env
:NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key
The authentication-related code is organized as follows:
src/
├── app/
│ ├── (auth)/
│ │ ├── callback/ # OAuth callback handling
│ │ ├── login/ # Login page
│ │ ├── register/ # Registration page
│ │ └── layout.tsx # Auth layout with session check
├── components/
│ ├── login-form.tsx # Login form component
│ └── register-form.tsx # Registration form component
└── lib/
└── supabase/
├── client.ts # Browser client initialization
└── server.ts # Server client initialization
// Login
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
// Registration
const { error } = await supabase.auth.signUp({
email,
password,
});
const { error } = await supabase.auth.signInWithOtp({
email,
});
const { error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
// lib/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";
export const createClient = () =>
createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// lib/supabase/server.ts
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
export const createClient = () => {
const cookieStore = cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
},
}
);
};
// app/(auth)/callback/route.ts
import { createClient } from "@/lib/supabase/server";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const requestUrl = new URL(request.url);
const code = requestUrl.searchParams.get("code");
if (code) {
const supabase = createClient();
await supabase.auth.exchangeCodeForSession(code);
}
return NextResponse.redirect(requestUrl.origin + "/dashboard");
}
// app/dashboard/page.tsx
import { createClient } from "@/lib/supabase/server";
import { redirect } from "next/navigation";
export default async function Dashboard() {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
redirect("/auth/login");
}
return (
// Dashboard content
);
}
The login form component (components/login-form.tsx
) provides:
The registration form component (components/register-form.tsx
) handles:
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (user) {
// User is authenticated
} else {
// User is not authenticated
}
const handleSignOut = async () => {
"use server";
const supabase = createClient();
await supabase.auth.signOut();
redirect("/auth/login");
};
Environment Variables
.env
for sensitive credentialsNEXT_PUBLIC_
Protected Routes
Error Handling
Session Management
The authentication system is integrated with the user profiles table:
CREATE TABLE public.profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
name TEXT,
email TEXT,
image TEXT,
customer_id TEXT,
price_id TEXT,
has_access BOOLEAN DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT (now() AT TIME ZONE 'UTC'),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT (now() AT TIME ZONE 'UTC')
);
-- Function to handle new user signup
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.profiles (id, email, name, image)
VALUES (
NEW.id,
NEW.email,
COALESCE(NEW.raw_user_meta_data->>'full_name', NEW.raw_user_meta_data->>'name'),
NEW.raw_user_meta_data->>'avatar_url'
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Trigger for automatic profile creation
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
Common issues and solutions:
Session Issues
OAuth Problems
Profile Creation