API Authentication
This guide covers authentication methods for the Artbase API.
Overview
Artbase uses different authentication methods depending on the API type:
| API Type | Auth Method | Use Case |
|---|---|---|
| Public | None | Gallery browsing, public data |
| User APIs | Supabase Auth | User-specific actions |
| Server APIs | API Key | Server-to-server |
| Webhooks | Signature verification | Incoming webhooks |
Supabase Authentication
Most user-facing APIs use Supabase Auth with JWT tokens.
Getting a Token
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'password',
});
// Get session token
const token = data.session?.access_token;
Using the Token
Include the token in the Authorization header:
curl -X GET "https://api.artbase.studio/v1/products" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Token Refresh
Tokens expire after 1 hour. Refresh automatically:
// Supabase client handles refresh automatically
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'TOKEN_REFRESHED') {
// New token available
}
});
API Keys
For server-to-server communication, use API keys.
Generating Keys
- Go to Creator Hub > Settings > API
- Click Generate New Key
- Copy the key (only shown once)
Using API Keys
curl -X GET "https://api.artbase.studio/v1/products" \
-H "Authorization: Bearer sk_live_abc123..."
Key Security
- Never expose keys in client-side code
- Store keys in environment variables
- Rotate keys periodically
- Use least-privilege keys when possible
Key Scopes
| Scope | Access |
|---|---|
read:products | Read products |
write:products | Create/update products |
read:orders | Read orders |
write:orders | Update orders |
admin | Full access |
Webhook Verification
Verify incoming webhooks using signatures.
Stripe Webhooks
import Stripe from 'stripe';
const stripe = new Stripe(STRIPE_SECRET_KEY);
export async function POST(request: Request) {
const body = await request.text();
const signature = request.headers.get('stripe-signature');
const event = stripe.webhooks.constructEvent(
body,
signature,
STRIPE_WEBHOOK_SECRET
);
// Process event
}
Custom Webhooks
For webhooks you send:
import crypto from 'crypto';
function verifySignature(payload: string, signature: string, secret: string) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
Organization Context
Most APIs require organization context:
Query Parameter
GET /api/products?org_id=org_abc123
Header
X-Organization-ID: org_abc123
From Session
The server extracts org from user's membership.
Error Responses
401 Unauthorized
{
"error": "Unauthorized",
"message": "Invalid or missing authentication token"
}
Causes:
- Missing Authorization header
- Expired token
- Invalid token format
403 Forbidden
{
"error": "Forbidden",
"message": "You don't have permission to access this resource"
}
Causes:
- User lacks required role
- Accessing another organization's data
- API key lacks required scope
Best Practices
Token Storage
// Good: HttpOnly cookie (for web)
// Good: Secure storage (for mobile)
// Bad: localStorage (XSS vulnerable)
Key Rotation
- Generate new key
- Update all services
- Verify new key works
- Revoke old key
Least Privilege
Create keys with minimal scopes:
// Good: specific scopes
const key = await createApiKey({
scopes: ['read:products', 'read:orders']
});
// Bad: admin access for simple tasks
const key = await createApiKey({
scopes: ['admin']
});