Authentication

How to authenticate with the Spray and Play API using session cookies, API keys, and JWT tokens.

Authentication

> The Spray and Play API uses session-based authentication for most endpoints,

> with API key support for metrics and admin operations. JWT token authentication is planned for v2.

Authentication Methods

1. Session-Based Authentication (Default)

Most API endpoints use cookie-based sessions via Supabase Auth. The browser handles this automatically:

// Include credentials for all authenticated requests
const response = await fetch('/api/user', {
  credentials: 'include'  // Required!
});

#### How It Works

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│    Login     │────▶│ Set Cookies  │────▶│  API Calls   │
│  (OAuth/Email)│     │(sb-access-token)│   │ (auto-auth)  │
└──────────────┘     └──────────────┘     └──────────────┘

1. User logs in via Supabase Auth (OAuth or email)

2. Session cookies are set automatically

3. Subsequent requests include the session cookie

4. API validates the session and returns user-specific data

#### Session Cookie Details

CookiePurposeExpiry
sb-access-tokenMain authentication token1 hour
sb-refresh-tokenToken refresh7 days
// React example with fetch
const fetchUserProfile = async () => {
  const response = await fetch('/api/user', {
    method: 'GET',
    credentials: 'include',  // Critical: includes session cookies
    headers: {
      'Content-Type': 'application/json'
    }
  });

  if (!response.ok) {
    if (response.status === 401) {
      // Redirect to login
      window.location.href = '/login';
    }
    throw new Error('Failed to fetch user');
  }

  return response.json();
};

---

2. API Key Authentication

Some endpoints (metrics, admin operations) require API keys:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://app.playtrenches.xyz/api/metrics
// TypeScript example
const fetchMetrics = async (apiKey: string) => {
  const response = await fetch('/api/metrics', {
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    }
  });

  if (!response.ok) {
    throw new Error(`Metrics fetch failed: ${response.status}`);
  }

  return response.json();
};

#### Endpoints Requiring API Keys

EndpointPurposeRequired Key
GET /api/metricsPerformance metricsAPI_KEY
GET /api/cron/*Cron jobsCRON_SECRET

---

3. JWT Authentication (v2 - Coming Soon)

Future API version will support JWT tokens:

// v2 API (coming soon)
const response = await fetch('/api/v2/user', {
  headers: {
    'Authorization': `Bearer ${jwtToken}`
  }
});

---

Required Headers

Standard Headers

HeaderValueRequired For
AuthorizationBearer YOUR_API_KEYMetrics, admin endpoints
Cookiesb-access-token=xxxSession-based auth
Content-Typeapplication/jsonPOST/PUT requests
Acceptapplication/jsonAll requests

CSRF Protection

For state-changing operations, include the CSRF token:

// Get CSRF token first
const csrfResponse = await fetch('/api/auth/csrf');
const { csrfToken } = await csrfResponse.json();

// Include in subsequent requests
const response = await fetch('/api/spray/v2', {
  method: 'POST',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify({ trenchId, amount })
});

---

Security Best Practices

> ### 🔒 API Key Security

>

> - Store API keys in environment variables

> - Never commit keys to version control

> - Rotate keys regularly

> - Monitor key usage in dashboard

>

> ### 🛡️ Session Security

>

> - Always use credentials: 'include' for authenticated requests

> - Implement proper session expiration handling

> - Use HTTPS in production

> - Implement logout functionality

>

> ### 📊 Monitoring

>

> - Track API key usage

> - Set up alerts for suspicious activity

> - Review authentication logs regularly

---

Error Responses

401 Unauthorized

{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication required"
  }
}

Resolution: Ensure user is logged in and credentials: 'include' is set.

403 Forbidden

{
  "success": false,
  "error": {
    "code": "FORBIDDEN",
    "message": "Insufficient permissions"
  }
}

Resolution: User lacks required role/permissions for this endpoint.

Invalid Session

{
  "success": false,
  "error": {
    "code": "SESSION_EXPIRED",
    "message": "Invalid or expired session"
  }
}

Resolution: Redirect to login page to refresh session.

Rate Limited (429)

{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests",
    "retry_after": 60
  }
}

Resolution: Wait for the specified retry_after seconds before retrying.

---

Complete Authentication Flow

// auth.ts - Complete authentication utilities

interface AuthConfig {
  baseUrl: string;
  apiKey?: string;
}

class SprayAndPlayAuth {
  private baseUrl: string;
  private apiKey?: string;

  constructor(config: AuthConfig) {
    this.baseUrl = config.baseUrl;
    this.apiKey = config.apiKey;
  }

  // Session-based authenticated request
  async authenticatedFetch(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<Response> {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      ...options,
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      }
    });

    if (response.status === 401) {
      // Session expired
      window.location.href = '/login';
      throw new Error('Session expired');
    }

    return response;
  }

  // API key authenticated request
  async apiKeyFetch(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<Response> {
    if (!this.apiKey) {
      throw new Error('API key required');
    }

    return fetch(`${this.baseUrl}${endpoint}`, {
      ...options,
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
  }

  // Check if user is authenticated
  async checkAuth(): Promise<boolean> {
    try {
      const response = await fetch(`${this.baseUrl}/api/user`, {
        credentials: 'include'
      });
      return response.ok;
    } catch {
      return false;
    }
  }

  // Logout
  async logout(): Promise<void> {
    await fetch(`${this.baseUrl}/api/auth/logout`, {
      method: 'POST',
      credentials: 'include'
    });
  }
}

// Usage
const auth = new SprayAndPlayAuth({
  baseUrl: 'https://app.playtrenches.xyz',
  apiKey: process.env.API_KEY
});

// Make authenticated request
const userData = await auth.authenticatedFetch('/api/user');

---

Environment Configuration

# .env.local
NEXT_PUBLIC_API_URL=https://app.playtrenches.xyz/api
API_KEY=your_api_key_here
CRON_SECRET=your_cron_secret_here
// config.ts
export const API_CONFIG = {
  baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api',
  apiKey: process.env.API_KEY,
  credentials: 'include' as RequestCredentials
};

---

Next Steps