Skip to main content

Authentication

Tether uses Supabase for authentication, providing secure and scalable user management out of the box.

Overview

Tether supports multiple authentication methods through Supabase:

  • Email and password
  • Magic links (passwordless)
  • OAuth providers (Google, GitHub, etc.)
  • Phone authentication

Setup Authentication

First, ensure your Supabase project is configured in .env.local:

1NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
2NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key

Email/Password Authentication

Sign Up

Create a new user with email and password:

1import { supabase } from '@/lib/supabase/client';
2
3async function signUp(email: string, password: string) {
4  const { data, error } = await supabase.auth.signUp({
5    email,
6    password,
7  });
8
9  if (error) {
10    console.error('Error signing up:', error.message);
11    return null;
12  }
13
14  return data;
15}

Sign In

Authenticate an existing user:

1import { supabase } from '@/lib/supabase/client';
2
3async function signIn(email: string, password: string) {
4  const { data, error } = await supabase.auth.signInWithPassword({
5    email,
6    password,
7  });
8
9  if (error) {
10    console.error('Error signing in:', error.message);
11    return null;
12  }
13
14  return data;
15}

Sign Out

Log out the current user:

1import { supabase } from '@/lib/supabase/client';
2
3async function signOut() {
4  const { error } = await supabase.auth.signOut();
5
6  if (error) {
7    console.error('Error signing out:', error.message);
8  }
9}

OAuth Authentication

Enable social login with OAuth providers:

1import { supabase } from '@/lib/supabase/client';
2
3async function signInWithGitHub() {
4  const { data, error } = await supabase.auth.signInWithOAuth({
5    provider: 'github',
6    options: {
7      redirectTo: `${window.location.origin}/auth/callback`,
8    },
9  });
10
11  if (error) {
12    console.error('Error with OAuth:', error.message);
13  }
14}
15
16// Also available: 'google', 'gitlab', 'bitbucket', etc.

Get Current User

Retrieve the currently authenticated user:

1import { supabase } from '@/lib/supabase/client';
2
3async function getCurrentUser() {
4  const { data: { user } } = await supabase.auth.getUser();
5  return user;
6}
7
8// Or get the session
9async function getSession() {
10  const { data: { session } } = await supabase.auth.getSession();
11  return session;
12}

Protected Routes

Tether uses cookie-based auth with @supabase/ssr. Middleware refreshes the session; server components check the user and redirect when needed.

1// middleware.ts - refresh session (required for cookie-based auth)
2import { createServerClient } from '@supabase/ssr';
3import { NextResponse, type NextRequest } from 'next/server';
4
5export async function middleware(request: NextRequest) {
6  let response = NextResponse.next({ request });
7
8  const supabase = createServerClient(
9    process.env.NEXT_PUBLIC_SUPABASE_URL!,
10    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
11    {
12      cookies: {
13        getAll: () => request.cookies.getAll(),
14        setAll: (cookies) =>
15          cookies.forEach(({ name, value, options }) =>
16            response.cookies.set(name, value, options)
17          ),
18      },
19    }
20  );
21
22  await supabase.auth.getUser(); // Refresh session if expired
23  return response;
24}
25
26export const config = {
27  matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'],
28};

In protected pages or layouts, use the server client to check auth and redirect:

1// app/dashboard/page.tsx (Server Component)
2import { createClient } from '@/lib/supabase/server';
3import { redirect } from 'next/navigation';
4
5export default async function DashboardPage() {
6  const supabase = await createClient();
7  const { data: { user } } = await supabase.auth.getUser();
8
9  if (!user) {
10    redirect('/login');
11  }
12
13  return <div>Welcome, {user.email}</div>;
14}

Auth State Changes

Listen for authentication state changes:

1import { useEffect } from 'react';
2import { supabase } from '@/lib/supabase/client';
3
4function useAuth() {
5  useEffect(() => {
6    const { data: { subscription } } = supabase.auth.onAuthStateChange(
7      (event, session) => {
8        console.log('Auth event:', event);
9        console.log('Session:', session);
10
11        if (event === 'SIGNED_IN') {
12          // Handle sign in
13        } else if (event === 'SIGNED_OUT') {
14          // Handle sign out
15        }
16      }
17    );
18
19    return () => {
20      subscription.unsubscribe();
21    };
22  }, []);
23}

Best Practices

  • Always validate user input before authentication
  • Use secure password requirements (min length, complexity)
  • Implement rate limiting to prevent brute force attacks
  • Store sensitive data only in environment variables
  • Use HTTPS in production
  • Implement proper error handling and user feedback

⚠️ Security Note

Never expose your service role key or other sensitive credentials in client-side code. Always use the anon key for client-side operations.

Next Steps