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
| Cookie | Purpose | Expiry |
|---|---|---|
sb-access-token | Main authentication token | 1 hour |
sb-refresh-token | Token refresh | 7 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
| Endpoint | Purpose | Required Key |
|---|---|---|
GET /api/metrics | Performance metrics | API_KEY |
GET /api/cron/* | Cron jobs | CRON_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
| Header | Value | Required For |
|---|---|---|
Authorization | Bearer YOUR_API_KEY | Metrics, admin endpoints |
Cookie | sb-access-token=xxx | Session-based auth |
Content-Type | application/json | POST/PUT requests |
Accept | application/json | All 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
- API Endpoints - Available endpoints and examples
- Webhooks - Real-time notifications
- Error Handling - Complete error reference