> ## Documentation Index
> Fetch the complete documentation index at: https://devperez08-platform-list-anime-54.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Complete guide to implementing and customizing authentication in EpiNeko

## Overview

EpiNeko uses [Supabase Auth](https://supabase.com/auth) for secure, scalable authentication. The system supports email/password authentication with a unique username-based login feature.

<Note>
  All authentication operations are handled through Next.js Server Actions, providing a seamless developer experience without the need for separate API routes.
</Note>

## Authentication Architecture

EpiNeko implements a multi-layered authentication system:

```mermaid theme={null}
graph TD
    A[User] --> B[Login Page]
    B --> C[Server Action]
    C --> D[Supabase Auth]
    D --> E{Valid?}
    E -->|Yes| F[Create Session]
    E -->|No| G[Redirect to Error]
    F --> H[Update Cookies]
    H --> I[Middleware]
    I --> J[Protected Route]
```

## Supabase Client Setup

EpiNeko uses two different Supabase clients depending on the execution context:

### Browser Client

Used in client components for client-side operations:

<CodeGroup>
  ```typescript src/utils/supabase/client.ts theme={null}
  import { createBrowserClient } from '@supabase/ssr'

  export function createClient() {
    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
    const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

    if (!supabaseUrl || !supabaseKey) {
      throw new Error('Missing Supabase environment variables: NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY');
    }

    return createBrowserClient(
      supabaseUrl,
      supabaseKey
    )
  }
  ```

  ```typescript Usage in Client Component theme={null}
  'use client'

  import { createClient } from '@/utils/supabase/client'

  export function MyClientComponent() {
    const supabase = createClient()
    
    // Use supabase client for client-side operations
    const handleClick = async () => {
      const { data } = await supabase.from('profiles').select()
    }
  }
  ```
</CodeGroup>

### Server Client

Used in server components and server actions for server-side operations:

<CodeGroup>
  ```typescript src/utils/supabase/server.ts theme={null}
  import { createServerClient } from '@supabase/ssr'
  import { cookies } from 'next/headers'

  export async function createClient() {
    const cookieStore = await cookies()

    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
    const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

    if (!supabaseUrl || !supabaseKey) {
      throw new Error('Missing Supabase environment variables');
    }

    return createServerClient(
      supabaseUrl,
      supabaseKey,
      {
        cookies: {
          getAll() {
            return cookieStore.getAll()
          },
          setAll(cookiesToSet) {
            try {
              cookiesToSet.forEach(({ name, value, options }) =>
                cookieStore.set(name, value, options)
              )
            } catch {
              // The `setAll` method was called from a Server Component.
              // This can be ignored if you have middleware refreshing
              // user sessions.
            }
          },
        },
      }
    )
  }
  ```

  ```typescript Usage in Server Component theme={null}
  import { createClient } from '@/utils/supabase/server'

  export default async function MyServerComponent() {
    const supabase = await createClient()
    const { data } = await supabase.from('profiles').select()
    
    return <div>{/* render data */}</div>
  }
  ```
</CodeGroup>

<Warning>
  Always use the appropriate client for your context. Using the browser client in server components or vice versa will cause errors.
</Warning>

## Authentication Flows

### User Signup

The signup flow creates a new user account and automatically generates a profile:

<CodeGroup>
  ```typescript src/app/login/actions.ts theme={null}
  'use server'

  import { revalidatePath } from 'next/cache'
  import { redirect } from 'next/navigation'
  import { createClient } from '@/utils/supabase/server'

  export async function signup(formData: FormData) {
    const supabase = await createClient()

    const email = formData.get('email') as string
    const password = formData.get('password') as string
    const username = formData.get('username') as string
    
    // Use the username from form or extract default from email
    const finalUsername = username || email.split('@')[0]

    const { error } = await supabase.auth.signUp({
      email,
      password,
      options: {
        data: {
          full_name: finalUsername,
          username: finalUsername,
        }
      }
    })

    if (error) {
      console.error('Signup error:', error.message)
      redirect('/error')
    }

    revalidatePath('/', 'layout')
    redirect('/')
  }
  ```

  ```tsx Signup Form Example theme={null}
  'use client'

  import { signup } from './actions'

  export function SignupForm() {
    return (
      <form action={signup}>
        <input
          name="email"
          type="email"
          placeholder="Email"
          required
        />
        <input
          name="username"
          type="text"
          placeholder="Username"
          minLength={3}
        />
        <input
          name="password"
          type="password"
          placeholder="Password"
          required
        />
        <button type="submit">Sign Up</button>
      </form>
    )
  }
  ```
</CodeGroup>

<Tip>
  User metadata (username and full\_name) is passed during signup and automatically used by the database trigger to create the profile.
</Tip>

### User Login

EpiNeko supports login with both email and username:

<CodeGroup>
  ```typescript src/app/login/actions.ts theme={null}
  export async function login(formData: FormData) {
    const supabase = await createClient()

    const identifier = formData.get('identifier') as string
    const password = formData.get('password') as string
    
    let email = identifier

    // If the identifier is not an email, look up the associated email
    if (!identifier.includes('@')) {
      const { data: profile } = await supabase
        .from('profiles')
        .select('email')
        .eq('username', identifier)
        .maybeSingle()
      
      if (profile?.email) {
        email = profile.email
      }
    }

    const { error } = await supabase.auth.signInWithPassword({
      email,
      password,
    })

    if (error) {
      console.error('Login error:', error.message)
      redirect('/error')
    }

    revalidatePath('/', 'layout')
    redirect('/')
  }
  ```

  ```tsx Login Form Example theme={null}
  'use client'

  import { login } from './actions'

  export function LoginForm() {
    return (
      <form action={login}>
        <input
          name="identifier"
          type="text"
          placeholder="Email or Username"
          required
        />
        <input
          name="password"
          type="password"
          placeholder="Password"
          required
        />
        <button type="submit">Log In</button>
      </form>
    )
  }
  ```
</CodeGroup>

<Steps>
  <Step title="User enters identifier">
    The user provides either an email or username along with their password
  </Step>

  <Step title="Identifier validation">
    The system checks if the identifier contains '@' to determine if it's an email
  </Step>

  <Step title="Username lookup">
    If it's a username, the system queries the profiles table to find the associated email
  </Step>

  <Step title="Authentication">
    Supabase Auth validates the email and password combination
  </Step>

  <Step title="Session creation">
    On success, a session is created and cookies are set
  </Step>
</Steps>

### User Logout

Simple logout functionality that clears the session:

<CodeGroup>
  ```typescript src/app/login/actions.ts theme={null}
  export async function signOut() {
    const supabase = await createClient()
    await supabase.auth.signOut()
    revalidatePath('/', 'layout')
    redirect('/')
  }
  ```

  ```tsx Logout Button Example theme={null}
  'use client'

  import { signOut } from '@/app/login/actions'

  export function LogoutButton() {
    return (
      <form action={signOut}>
        <button type="submit">Sign Out</button>
      </form>
    )
  }
  ```
</CodeGroup>

## Session Management

### Middleware

EpiNeko uses Next.js middleware to automatically refresh user sessions on every request:

<CodeGroup>
  ```typescript src/middleware.ts theme={null}
  import { type NextRequest } from 'next/server'
  import { updateSession } from '@/utils/supabase/middleware'

  export async function middleware(request: NextRequest) {
    return await updateSession(request)
  }

  export const config = {
    matcher: [
      /*
       * Match all request paths except for the ones starting with:
       * - _next/static (static files)
       * - _next/image (image optimization files)
       * - favicon.ico (favicon file)
       * Feel free to modify this pattern to include more paths.
       */
      '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
    ],
  }
  ```

  ```typescript src/utils/supabase/middleware.ts theme={null}
  import { createServerClient } from '@supabase/ssr'
  import { NextResponse, type NextRequest } from 'next/server'

  export async function updateSession(request: NextRequest) {
    let supabaseResponse = NextResponse.next({
      request,
    })

    const supabase = createServerClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
      {
        cookies: {
          getAll() {
            return request.cookies.getAll()
          },
          setAll(cookiesToSet) {
            cookiesToSet.forEach(({ name, value, options }) => 
              request.cookies.set(name, value))
            supabaseResponse = NextResponse.next({
              request,
            })
            cookiesToSet.forEach(({ name, value, options }) =>
              supabaseResponse.cookies.set(name, value, options)
            )
          },
        },
      }
    )

    // Refreshing the auth token
    await supabase.auth.getUser()

    return supabaseResponse
  }
  ```
</CodeGroup>

<Note>
  The middleware automatically refreshes the authentication token on every request, ensuring users stay logged in without manual intervention.
</Note>

## Profile Management

### Automatic Profile Creation

Profiles are automatically created when a user signs up through a database trigger:

```sql supabase/migrations/20260218_initial_schema.sql theme={null}
-- Profiles Table
create table public.profiles (
  id uuid references auth.users on delete cascade not null primary key,
  updated_at timestamp with time zone,
  username text unique,
  full_name text,
  avatar_url text,
  website text,
  constraint username_length check (char_length(username) >= 3)
);

-- Trigger function
create or replace function public.handle_new_user()
returns trigger as $$
begin
  insert into public.profiles (id, full_name, username, avatar_url)
  values (
    new.id, 
    new.raw_user_meta_data->>'full_name', 
    new.raw_user_meta_data->>'username',
    new.raw_user_meta_data->>'avatar_url'
  );
  return new;
end;
$$ language plpgsql security definer;

-- Trigger
create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();
```

### Row Level Security (RLS)

Profiles are protected with Row Level Security policies:

```sql theme={null}
-- Enable RLS
alter table public.profiles enable row level security;

-- Public profiles are viewable by everyone
create policy "Public profiles are viewable by everyone." on public.profiles
  for select using (true);

-- Users can insert their own profile
create policy "Users can insert their own profile." on public.profiles
  for insert with check (auth.uid() = id);

-- Users can update own profile
create policy "Users can update own profile." on public.profiles
  for update using (auth.uid() = id);
```

<Tip>
  RLS policies ensure that users can only modify their own profiles while still allowing public viewing of all profiles.
</Tip>

## Getting the Current User

Fetch the authenticated user in server components:

<CodeGroup>
  ```typescript Server Component theme={null}
  import { createClient } from '@/utils/supabase/server'

  export default async function ProtectedPage() {
    const supabase = await createClient()
    
    const { data: { user } } = await supabase.auth.getUser()
    
    if (!user) {
      redirect('/login')
    }
    
    return <div>Welcome, {user.email}</div>
  }
  ```

  ```typescript Server Action theme={null}
  'use server'

  import { createClient } from '@/utils/supabase/server'

  export async function getUserProfile() {
    const supabase = await createClient()
    
    const { data: { user } } = await supabase.auth.getUser()
    
    if (!user) return null
    
    const { data: profile } = await supabase
      .from('profiles')
      .select('*')
      .eq('id', user.id)
      .single()
    
    return profile
  }
  ```
</CodeGroup>

## Protecting Routes

Create protected routes by checking authentication status:

```typescript app/protected/page.tsx theme={null}
import { createClient } from '@/utils/supabase/server'
import { redirect } from 'next/navigation'

export default async function ProtectedPage() {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
  
  if (!user) {
    redirect('/login')
  }
  
  // Protected content
  return <div>This is a protected page</div>
}
```

<Warning>
  Always validate authentication on the server side, never rely solely on client-side checks for security.
</Warning>

## Customization

### Adding Social Authentication

To add OAuth providers (Google, GitHub, etc.):

1. Enable the provider in Supabase Dashboard (Authentication > Providers)
2. Add the OAuth redirect URL to your provider's app settings
3. Update your login page with social buttons:

```typescript theme={null}
export async function signInWithGoogle() {
  const supabase = await createClient()
  
  const { error } = await supabase.auth.signInWithOAuth({
    provider: 'google',
    options: {
      redirectTo: `${location.origin}/auth/callback`,
    },
  })
}
```

### Email Verification

Enable email verification in Supabase Dashboard (Authentication > Email Templates):

```typescript theme={null}
const { error } = await supabase.auth.signUp({
  email,
  password,
  options: {
    emailRedirectTo: `${location.origin}/auth/callback`,
  }
})
```

### Password Recovery

Implement password reset:

```typescript theme={null}
export async function resetPassword(email: string) {
  const supabase = createClient()
  
  const { error } = await supabase.auth.resetPasswordForEmail(email, {
    redirectTo: `${location.origin}/auth/reset-password`,
  })
}
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Session not persisting" icon="cookie">
    Ensure middleware is properly configured and running on all routes. Check that cookies are being set correctly.
  </Accordion>

  <Accordion title="Username login not working" icon="user">
    Verify that the username is stored in the profiles table and is properly indexed. Check that the email column is added to profiles.
  </Accordion>

  <Accordion title="Profile not created on signup" icon="database">
    Check that the database trigger is installed correctly. Verify that user metadata is being passed during signup.
  </Accordion>
</AccordionGroup>
