Documentation is currently in beta. Report issues →
ArchitectureAuthentication

Authentication

Y3NKO uses Supabase Auth for authentication, providing multiple sign-in methods and session management.

Overview

Supabase Auth provides:

  • Email/password authentication
  • OAuth providers (Google)
  • Magic links
  • Session management
  • User metadata storage

Authentication Flow

User clicks "Sign In"


┌─────────────────────┐
│    Login Page       │
│  - Email/Password   │
│  - Google OAuth     │
│  - Magic Link       │
└─────────────────────┘


┌─────────────────────┐
│   Supabase Auth     │
│  - Validates creds  │
│  - Creates session  │
└─────────────────────┘


┌─────────────────────┐
│   Callback Route    │
│  - Exchange code    │
│  - Set cookies      │
└─────────────────────┘


┌─────────────────────┐
│   Redirect to       │
│   Dashboard/Home    │
└─────────────────────┘

Supabase Client Setup

Server Client

// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
 
export async function createClient() {
  const cookieStore = await cookies()
 
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll()
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options)
          )
        },
      },
    }
  )
}

Browser Client

// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr'
 
export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

Getting the Current User

In Server Components

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

In Server Actions

"use server"
 
import { createClient } from '@/lib/supabase/server'
 
export async function updateProfile(data: ProfileData) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
 
  if (!user) {
    throw new Error('Unauthorized')
  }
 
  // Update profile...
}

In Client Components

"use client"
 
import { createClient } from '@/lib/supabase/client'
import { useEffect, useState } from 'react'
 
export function UserMenu() {
  const [user, setUser] = useState(null)
  const supabase = createClient()
 
  useEffect(() => {
    supabase.auth.getUser().then(({ data: { user } }) => {
      setUser(user)
    })
  }, [])
 
  return user ? <ProfileDropdown user={user} /> : <SignInButton />
}

Protected Routes

Using Middleware

// middleware.ts
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
 
export async function middleware(request: NextRequest) {
  const response = NextResponse.next()
 
  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 }) =>
            response.cookies.set(name, value, options)
          )
        },
      },
    }
  )
 
  const { data: { user } } = await supabase.auth.getUser()
 
  // Protected routes
  if (request.nextUrl.pathname.startsWith('/dashboard') && !user) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
 
  if (request.nextUrl.pathname.startsWith('/admin') && !user) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
 
  return response
}
 
export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*', '/host/:path*'],
}

Role-Based Access

Users have roles stored in the database:

enum UserRole {
  USER = 'USER',
  HOST = 'HOST',
  ADMIN = 'ADMIN',
  SUPER_ADMIN = 'SUPER_ADMIN'
}

Checking Roles

// lib/auth.ts
import { createClient } from '@/lib/supabase/server'
import { prisma } from '@/lib/db'
 
export async function getCurrentUser() {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
 
  if (!user) return null
 
  return await prisma.user.findUnique({
    where: { id: user.id },
    include: { profile: true }
  })
}
 
export async function requireRole(roles: UserRole[]) {
  const user = await getCurrentUser()
 
  if (!user || !roles.includes(user.role)) {
    throw new Error('Forbidden')
  }
 
  return user
}

Usage in Pages

// app/admin/page.tsx
import { requireRole } from '@/lib/auth'
 
export default async function AdminPage() {
  const user = await requireRole(['ADMIN', 'SUPER_ADMIN'])
 
  return <AdminDashboard user={user} />
}

Sign Out

"use client"
 
import { createClient } from '@/lib/supabase/client'
import { useRouter } from 'next/navigation'
 
export function SignOutButton() {
  const router = useRouter()
  const supabase = createClient()
 
  async function handleSignOut() {
    await supabase.auth.signOut()
    router.push('/')
    router.refresh()
  }
 
  return <button onClick={handleSignOut}>Sign Out</button>
}

OAuth Configuration

Google OAuth

  1. Configure in Supabase Dashboard > Authentication > Providers
  2. Add redirect URLs:
    • http://localhost:3000/auth/callback (development)
    • https://y3nko.travel/auth/callback (production)

Callback Route

// app/auth/callback/route.ts
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
 
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const code = searchParams.get('code')
  const next = searchParams.get('next') ?? '/dashboard'
 
  if (code) {
    const supabase = await createClient()
    const { error } = await supabase.auth.exchangeCodeForSession(code)
 
    if (!error) {
      return NextResponse.redirect(new URL(next, request.url))
    }
  }
 
  return NextResponse.redirect(new URL('/auth/error', request.url))
}

Supabase handles token refresh automatically. Sessions are stored in HTTP-only cookies for security.