Galey Cloud uses Supabase Authentication to secure user accounts and ensure each user can only access their own photos and albums. The authentication system includes sign up, login, session management, and route protection.
Authentication Setup
Supabase Client Configuration
Galey Cloud uses two Supabase clients: one for client-side operations and one for server-side:
Client-Side
import { createBrowserClient } from '@supabase/ssr'
export function createClient () {
return createBrowserClient (
process . env . NEXT_PUBLIC_SUPABASE_URL ! ,
process . env . NEXT_PUBLIC_SUPABASE_ANON_KEY ! ,
)
}
Location: lib/supabase/client.ts:3-8
Server-Side
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 ) {
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.
}
},
},
},
)
}
Location: lib/supabase/server.ts:9-34
The server-side client properly handles Next.js cookies for secure session management. Always create a new client instance for each request (important for Vercel Fluid compute).
Sign Up Flow
The sign-up page allows new users to create an account:
'use client'
import { createClient } from '@/lib/supabase/client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
export default function Page () {
const [ email , setEmail ] = useState ( '' )
const [ password , setPassword ] = useState ( '' )
const [ repeatPassword , setRepeatPassword ] = useState ( '' )
const [ error , setError ] = useState < string | null >( null )
const [ isLoading , setIsLoading ] = useState ( false )
const router = useRouter ()
const handleSignUp = async ( e : React . FormEvent ) => {
e . preventDefault ()
const supabase = createClient ()
setIsLoading ( true )
setError ( null )
if ( password !== repeatPassword ) {
setError ( 'Las contrasenas no coinciden' )
setIsLoading ( false )
return
}
try {
const { error } = await supabase . auth . signUp ({
email ,
password ,
options: {
emailRedirectTo:
process . env . NEXT_PUBLIC_DEV_SUPABASE_REDIRECT_URL ||
` ${ window . location . origin } /gallery` ,
},
})
if ( error ) throw error
router . push ( '/auth/sign-up-success' )
} catch ( error : unknown ) {
setError ( error instanceof Error ? error . message : 'Ocurrio un error' )
} finally {
setIsLoading ( false )
}
}
return (
< form onSubmit = { handleSignUp } >
{ /* Form fields */ }
</ form >
)
}
Location: app/auth/sign-up/page.tsx:18-55
User enters email and password
Password must be entered twice for confirmation
Passwords are validated
Client-side check ensures passwords match
Supabase creates account
User record is created with email verification link sent
Redirect to success page
User is shown confirmation message to check email
Login Flow
The login page authenticates existing users:
'use client'
import { createClient } from '@/lib/supabase/client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
export default function Page () {
const [ email , setEmail ] = useState ( '' )
const [ password , setPassword ] = useState ( '' )
const [ error , setError ] = useState < string | null >( null )
const [ isLoading , setIsLoading ] = useState ( false )
const router = useRouter ()
const handleLogin = async ( e : React . FormEvent ) => {
e . preventDefault ()
const supabase = createClient ()
setIsLoading ( true )
setError ( null )
try {
const { error } = await supabase . auth . signInWithPassword ({
email ,
password ,
})
if ( error ) throw error
router . push ( '/gallery' )
} catch ( error : unknown ) {
setError ( error instanceof Error ? error . message : 'Ocurrio un error' )
} finally {
setIsLoading ( false )
}
}
return (
< form onSubmit = { handleLogin } >
{ /* Form fields */ }
</ form >
)
}
Location: app/auth/login/page.tsx:18-43
User enters credentials
Email and password are submitted via form
Supabase validates credentials
signInWithPassword checks against stored user data
Session created
Authentication cookies are set automatically
Redirect to gallery
User is redirected to /gallery on successful login
Session Management
Middleware Session Refresh
The middleware automatically refreshes user sessions on each request:
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 }) =>
request . cookies . set ( name , value ),
)
supabaseResponse = NextResponse . next ({
request ,
})
cookiesToSet . forEach (({ name , value , options }) =>
supabaseResponse . cookies . set ( name , value , options ),
)
},
},
},
)
// IMPORTANT: getUser() must be called to refresh the session
const {
data : { user },
} = await supabase . auth . getUser ()
if (
request . nextUrl . pathname . startsWith ( '/gallery' ) &&
! user
) {
const url = request . nextUrl . clone ()
url . pathname = '/auth/login'
return NextResponse . redirect ( url )
}
return supabaseResponse
}
Location: lib/supabase/middleware.ts:4-69
Critical: Always call supabase.auth.getUser() in middleware. Skipping this can cause users to be randomly logged out due to stale sessions.
Middleware Configuration
import { updateSession } from '@/lib/supabase/middleware'
import { type NextRequest } from 'next/server'
export async function middleware ( request : NextRequest ) {
return await updateSession ( request )
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.* \\ .(?:svg|png|jpg|jpeg|gif|webp)$).*)' ,
],
}
Location: middleware.ts:4-20
The matcher ensures middleware runs on all routes except static assets and images.
Protected Routes
Route Protection
The /gallery route is protected by the middleware:
if (
request . nextUrl . pathname . startsWith ( '/gallery' ) &&
! user
) {
const url = request . nextUrl . clone ()
url . pathname = '/auth/login'
return NextResponse . redirect ( url )
}
Location: lib/supabase/middleware.ts:44-52
Unauthenticated users attempting to access /gallery are automatically redirected to the login page.
API Route Protection
All API routes validate authentication:
export async function GET () {
const supabase = await createClient ()
const { data : { user } } = await supabase . auth . getUser ()
if ( ! user ) {
return NextResponse . json ({ error: 'No autorizado' }, { status: 401 })
}
// Continue with authenticated logic...
}
Example from: app/api/photos/route.ts:4-10
Row-Level Security (RLS)
Supabase’s row-level security ensures users can only access their own data. All database queries include user verification:
// Only fetch photos belonging to the authenticated user
const { data , error } = await supabase
. from ( 'photos' )
. select ( '*' )
. eq ( 'user_id' , user . id )
. order ( 'created_at' , { ascending: false })
Example from: app/api/photos/route.ts:12-16
Photos API .eq('user_id', user.id) filters photos
Albums API .eq('user_id', user.id) filters albums
Logout
Users can sign out from the gallery sidebar:
const handleLogout = useCallback ( async () => {
const supabase = createClient ()
await supabase . auth . signOut ()
router . push ( '/auth/login' )
}, [ router ])
Location: app/gallery/page.tsx:163-167
User clicks logout button
Located in the AlbumSidebar component
Supabase session cleared
signOut() removes authentication cookies
Redirect to login
User is sent back to the login page
Environment Variables
Authentication requires these environment variables:
NEXT_PUBLIC_SUPABASE_URL = https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY = your-anon-key
The NEXT_PUBLIC_ prefix makes these variables accessible in both client and server code. The anon key is safe to expose publicly as Supabase uses RLS to protect data.
Authentication Flow Diagram
Security Best Practices
Always use server-side client for API routes
API routes should use createClient() from lib/supabase/server.ts to ensure proper cookie handling and session validation.
Validate user on every API call
Never trust client-side authentication. Always call supabase.auth.getUser() and check for a valid user before processing requests.
Use RLS in database queries
Always filter by user_id to ensure users can only access their own data, even if RLS policies are configured in Supabase.
Don't skip getUser() in middleware
Calling getUser() is critical for session refresh. Omitting it can cause random logout issues.
Related Pages
Gallery Protected gallery interface
Photos API API routes with authentication