- Removed checkAuth() function and redirects from dashboard.astro - Removed checkAuth() function and redirects from events/new.astro - Updated to use Astro.cookies for better SSR compatibility - Client-side code now focuses on data loading, not authentication - Server-side unified auth system handles all protection 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
298 lines
7.8 KiB
Markdown
298 lines
7.8 KiB
Markdown
# Authentication System Documentation
|
|
|
|
## Overview
|
|
|
|
Black Canyon Tickets uses a unified authentication system built on top of Supabase Auth with server-side rendering (SSR) support. All authentication functionality is centralized in a single module (`src/lib/auth-unified.ts`) to ensure consistency and maintainability.
|
|
|
|
## Architecture
|
|
|
|
### Core Module: `auth-unified.ts`
|
|
|
|
The unified auth module is the single source of truth for all authentication in the application. It provides:
|
|
|
|
- 🔐 **Session-based authentication** using Supabase cookies
|
|
- 🎭 **Role-based access control** (User, Admin, Super Admin)
|
|
- 🏢 **Organization-based access control**
|
|
- 🛡️ **Security logging and rate limiting**
|
|
- 📝 **Type-safe authentication context**
|
|
- 🐳 **Docker-friendly cookie handling**
|
|
|
|
### Key Features
|
|
|
|
1. **Universal Compatibility**: Works with both `Request` objects (API routes) and `AstroCookies` (Astro pages)
|
|
2. **SSR Support**: Full server-side rendering support with proper cookie handling
|
|
3. **Bearer Token Fallback**: Supports Authorization header for API clients
|
|
4. **Security Hardening**: Built-in CSRF protection, rate limiting, and security logging
|
|
5. **Type Safety**: Full TypeScript support with proper types
|
|
|
|
## Usage Guide
|
|
|
|
### Basic Authentication Check
|
|
|
|
```typescript
|
|
// In Astro pages (.astro files)
|
|
import { verifyAuth } from '../lib/auth-unified';
|
|
|
|
// Check if user is authenticated (returns null if not)
|
|
const auth = await verifyAuth(Astro.cookies);
|
|
|
|
// In API routes (.ts files)
|
|
const auth = await verifyAuth(request);
|
|
```
|
|
|
|
### Requiring Authentication
|
|
|
|
```typescript
|
|
// Throws AuthError if not authenticated
|
|
const auth = await requireAuth(Astro.cookies);
|
|
|
|
// The auth object contains:
|
|
// - user: Supabase user object
|
|
// - session: Session information
|
|
// - isAdmin: boolean
|
|
// - isSuperAdmin: boolean
|
|
// - organizationId: string | null
|
|
```
|
|
|
|
### Role-Based Access Control
|
|
|
|
```typescript
|
|
// Require admin access
|
|
const auth = await requireAdmin(Astro.cookies);
|
|
|
|
// Require super admin access
|
|
const auth = await requireSuperAdmin(Astro.cookies);
|
|
|
|
// Check organization access
|
|
const auth = await requireOrganizationAccess(Astro.cookies, organizationId);
|
|
```
|
|
|
|
### Protected Page Pattern
|
|
|
|
```typescript
|
|
---
|
|
import Layout from '../layouts/Layout.astro';
|
|
import { verifyAuth } from '../lib/auth-unified';
|
|
|
|
export const prerender = false; // Enable SSR
|
|
|
|
const auth = await verifyAuth(Astro.cookies);
|
|
if (!auth) {
|
|
return Astro.redirect('/login');
|
|
}
|
|
---
|
|
|
|
<Layout title="Protected Page">
|
|
<h1>Welcome, {auth.user.email}!</h1>
|
|
</Layout>
|
|
```
|
|
|
|
### Protected API Route Pattern
|
|
|
|
```typescript
|
|
import type { APIRoute } from 'astro';
|
|
import { requireAuth, createAuthResponse } from '../lib/auth-unified';
|
|
|
|
export const GET: APIRoute = async ({ request }) => {
|
|
try {
|
|
const auth = await requireAuth(request);
|
|
|
|
// Your protected logic here
|
|
const data = { userId: auth.user.id };
|
|
|
|
return createAuthResponse(data);
|
|
} catch (error) {
|
|
return createAuthResponse(
|
|
{ error: error.message },
|
|
error.statusCode || 401
|
|
);
|
|
}
|
|
};
|
|
```
|
|
|
|
### Using the Middleware Pattern
|
|
|
|
```typescript
|
|
import { withAuth } from '../lib/auth-unified';
|
|
|
|
// Wrap your handler with authentication
|
|
export const GET: APIRoute = async ({ request }) => {
|
|
return withAuth(request, async (auth) => {
|
|
// This code only runs if authenticated
|
|
return new Response(JSON.stringify({
|
|
message: `Hello ${auth.user.email}`
|
|
}));
|
|
});
|
|
};
|
|
```
|
|
|
|
## Authentication Flow
|
|
|
|
1. **Login**: User submits credentials → `/api/auth/login` → Supabase sets cookies → Redirect to dashboard
|
|
2. **Session Check**: Every request → `verifyAuth()` checks cookies → Returns auth context or null
|
|
3. **Protected Routes**: Page/API checks auth → Redirects to login or returns 401 if not authenticated
|
|
4. **Logout**: Clear session → `/api/auth/logout` → Supabase clears cookies → Redirect to login
|
|
|
|
## Error Handling
|
|
|
|
The auth system uses a custom `AuthError` class with specific error codes:
|
|
|
|
```typescript
|
|
try {
|
|
const auth = await requireAuth(request);
|
|
} catch (error) {
|
|
if (error instanceof AuthError) {
|
|
switch (error.code) {
|
|
case 'NO_SESSION':
|
|
// User not logged in
|
|
break;
|
|
case 'NO_PERMISSION':
|
|
// User lacks required role
|
|
break;
|
|
case 'EXPIRED':
|
|
// Session expired
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Security Features
|
|
|
|
### CSRF Protection
|
|
|
|
```typescript
|
|
// Generate token for forms
|
|
const csrfToken = generateCSRFToken();
|
|
|
|
// Verify token on submission
|
|
if (!verifyCSRFToken(request, sessionToken)) {
|
|
throw new Error('Invalid CSRF token');
|
|
}
|
|
```
|
|
|
|
### Rate Limiting
|
|
|
|
```typescript
|
|
// Check rate limit (10 requests per minute by default)
|
|
const identifier = `login:${email}`;
|
|
if (!checkRateLimit(identifier, 5, 60000)) {
|
|
throw new Error('Too many attempts');
|
|
}
|
|
```
|
|
|
|
### Security Logging
|
|
|
|
All authentication events are automatically logged:
|
|
- Failed login attempts
|
|
- Successful authentications
|
|
- Permission denied events
|
|
- Rate limit violations
|
|
|
|
## Testing
|
|
|
|
Visit `/auth-test-unified` to test the authentication system. This page shows:
|
|
- Current authentication status
|
|
- Session information
|
|
- Request headers and cookies
|
|
- Links to test protected routes
|
|
|
|
## Migration Guide
|
|
|
|
### From Old Auth System
|
|
|
|
1. **Update imports**:
|
|
```typescript
|
|
// Old
|
|
import { verifyAuth } from '../lib/auth';
|
|
|
|
// New (but old path still works via proxy)
|
|
import { verifyAuth } from '../lib/auth-unified';
|
|
```
|
|
|
|
2. **Use Astro.cookies instead of Astro.request**:
|
|
```typescript
|
|
// Old
|
|
const auth = await verifyAuth(Astro.request);
|
|
|
|
// New (better SSR support)
|
|
const auth = await verifyAuth(Astro.cookies);
|
|
```
|
|
|
|
3. **Handle new error types**:
|
|
```typescript
|
|
// Old
|
|
if (!auth) throw new Error('Not authenticated');
|
|
|
|
// New
|
|
import { AuthError } from '../lib/auth-unified';
|
|
if (!auth) throw new AuthError('Not authenticated', 'NO_SESSION');
|
|
```
|
|
|
|
## Docker Considerations
|
|
|
|
The unified auth system is designed to work seamlessly in Docker environments:
|
|
|
|
1. **Cookie Domain**: Automatically handles cookie domain settings
|
|
2. **Secure Cookies**: Uses secure cookies in production
|
|
3. **Proxy Support**: Correctly reads IP addresses behind proxies
|
|
|
|
## Environment Variables
|
|
|
|
```bash
|
|
# Required for auth to work
|
|
PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
|
PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
SUPABASE_SERVICE_ROLE_KEY=your-service-key
|
|
|
|
# Optional for enhanced security
|
|
COOKIE_DOMAIN=.yourdomain.com # For subdomain support
|
|
NODE_ENV=production # Enables secure cookies
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **"No session found" after login**
|
|
- Check cookie settings in browser
|
|
- Verify Supabase URL and keys are correct
|
|
- Ensure cookies are not blocked
|
|
|
|
2. **Dashboard flashing before redirect**
|
|
- Ensure using `Astro.cookies` not `Astro.request`
|
|
- Verify `prerender = false` is set
|
|
- Check server-side auth is enabled
|
|
|
|
3. **Auth works locally but not in Docker**
|
|
- Check `COOKIE_DOMAIN` environment variable
|
|
- Verify proxy headers are being forwarded
|
|
- Ensure secure cookie settings match environment
|
|
|
|
### Debug Mode
|
|
|
|
Enable debug logging in development:
|
|
|
|
```typescript
|
|
// In your page or API route
|
|
import { authDebug } from '../lib/auth-unified';
|
|
|
|
authDebug.logCookies(request);
|
|
authDebug.logSession(session);
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Always use the unified auth module** - Don't create separate auth implementations
|
|
2. **Use `Astro.cookies` for pages** - Better SSR support than `Astro.request`
|
|
3. **Handle errors gracefully** - Show user-friendly messages, not technical errors
|
|
4. **Test auth flows regularly** - Use `/auth-test-unified` to verify functionality
|
|
5. **Keep sessions secure** - Use HTTPS in production, set proper cookie flags
|
|
|
|
## Future Enhancements
|
|
|
|
- [ ] Refresh token rotation
|
|
- [ ] Remember me functionality
|
|
- [ ] Two-factor authentication
|
|
- [ ] Session activity tracking
|
|
- [ ] IP-based session validation |