Files
blackcanyontickets/src/lib/supabase-ssr.ts
dzinesco 57b23a304c fix: Resolve Supabase auth loop and implement secure authentication system
This commit fixes the persistent login/redirect loop issue and implements
a robust authentication system for the Docker/localhost environment.

Key Changes:
- Environment-aware cookie configuration in supabase-ssr.ts
- New AuthLoader component to prevent content flashing during auth checks
- Cleaned up login page client-side auth logic to prevent redirect loops
- Updated dashboard to use AuthLoader for smooth authentication experience

Technical Details:
- Cookies now use environment-appropriate security settings
- Server-side auth verification eliminates client-side timing issues
- Loading states provide better UX during auth transitions
- Unified authentication pattern across all protected pages

Fixes:
- Dashboard no longer flashes before auth redirect
- Login page loads cleanly without auth checking loops
- Cookie configuration works correctly in Docker localhost
- No more redirect loops between login and dashboard pages

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-12 21:40:41 -06:00

87 lines
2.7 KiB
TypeScript

import { createServerClient, type CookieOptions } from '@supabase/ssr'
import type { Database } from './database.types'
import type { AstroCookies } from 'astro'
export function createSupabaseServerClient(
cookies: AstroCookies,
cookieOptions?: CookieOptions
) {
// Environment-aware cookie configuration
const isProduction = import.meta.env.PROD || process.env.NODE_ENV === 'production';
// For Docker/localhost, always use non-secure cookies
// In production, this will be overridden to use secure cookies
const useSecureCookies = isProduction;
const defaultCookieOptions: CookieOptions = {
secure: useSecureCookies, // secure in production, non-secure for localhost
sameSite: 'lax', // allow cross-site cookie on navigation
path: '/', // root-wide access
httpOnly: true, // JS-inaccessible for security
maxAge: 60 * 60 * 24 * 7, // 7 days
};
return createServerClient<Database>(
import.meta.env.PUBLIC_SUPABASE_URL!,
import.meta.env.PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
if (!cookies) return undefined;
return cookies.get(name)?.value
},
set(name: string, value: string, options: CookieOptions) {
if (!cookies) return;
// Merge with default options, allowing overrides
cookies.set(name, value, {
...defaultCookieOptions,
...cookieOptions,
...options,
})
},
remove(name: string, options: CookieOptions) {
if (!cookies) return;
cookies.delete(name, {
...defaultCookieOptions,
...options,
})
},
},
cookieOptions: defaultCookieOptions,
}
)
}
// Helper to create a client from Request headers (for API routes)
export function createSupabaseServerClientFromRequest(request: Request) {
const cookieHeader = request.headers.get('Cookie') || ''
return createServerClient<Database>(
import.meta.env.PUBLIC_SUPABASE_URL!,
import.meta.env.PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
const cookies = parseCookies(cookieHeader)
return cookies[name]
},
set() {
// Can't set cookies in request context
},
remove() {
// Can't remove cookies in request context
},
},
}
)
}
function parseCookies(cookieHeader: string): Record<string, string> {
return cookieHeader.split(';').reduce((acc, cookie) => {
const [key, value] = cookie.trim().split('=')
if (key && value) {
acc[key] = decodeURIComponent(value)
}
return acc
}, {} as Record<string, string>)
}