Skip to main content

Authentication Methods

Sparklytics supports multiple authentication modes for different deployment scenarios.

Auth Modes

Configure via SPARKLYTICS_AUTH environment variable:
SPARKLYTICS_AUTH
string
default:"local"
Authentication mode: none, password, or local
First-run experience:
  1. Navigate to dashboard → redirected to /setup
  2. Create admin password (one-time)
  3. Login with password
Features:
  • Admin password stored as Argon2id hash in database
  • Password change endpoint available
  • Session cookie authentication
  • API key generation
Configuration:
SPARKLYTICS_AUTH=local
SPARKLYTICS_SESSION_DAYS=7  # JWT cookie lifetime (1-30 days)

Password Mode

Single shared password for all users, configured via environment variable. Features:
  • No first-run setup page
  • Password cannot be changed via API (env-only)
  • Session cookie authentication
  • API key generation
Configuration:
SPARKLYTICS_AUTH=password
SPARKLYTICS_PASSWORD=your-secure-password
SPARKLYTICS_SESSION_DAYS=7

None Mode

No authentication required (development/private networks only). Features:
  • Dashboard accessible without login
  • Auth endpoints return 404 Not Found
  • No API keys
Configuration:
SPARKLYTICS_AUTH=none
Never use none mode in production. Your analytics data will be publicly accessible.

Session Authentication

Login Flow

Request:
curl -X POST https://analytics.example.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "password": "your-password"
  }'
Response (200 OK):
{
  "data": {
    "expires_at": "2026-03-10T12:00:00Z"
  }
}
Cookie set:
Set-Cookie: spk_session=eyJhbGc...; HttpOnly; SameSite=Strict; Path=/; Max-Age=604800; Secure
spk_session
string
JWT token containing session claims
Cookie attributes:
  • HttpOnly - Prevents JavaScript access (XSS protection)
  • SameSite=Strict - CSRF protection
  • Path=/ - Valid for all endpoints
  • Max-Age - Calculated from SPARKLYTICS_SESSION_DAYS (default: 7 days = 604,800 seconds)
  • Secure - Only sent over HTTPS (controlled by SPARKLYTICS_HTTPS, defaults to true)
Set SPARKLYTICS_HTTPS=false only for local development on plain HTTP. Production deployments must use HTTPS.

JWT Token Structure

Claims:
{
  "sub": "admin",
  "exp": 1709211600,
  "iat": 1708606800
}
sub
string
Subject (always admin in self-hosted mode)
exp
number
Expiration timestamp (Unix seconds)
iat
number
Issued-at timestamp (Unix seconds)
Signing:
  • Algorithm: HS256 (HMAC with SHA-256)
  • Secret: 64-character hex string (auto-generated, stored in database)
  • Secret rotation: automatic on password change (invalidates all sessions)

Session Management

Check Authentication Status

curl https://analytics.example.com/api/auth/status
Response:
{
  "mode": "local",
  "setup_required": false,
  "authenticated": true
}
mode
string
Auth mode: local or password (returns 404 in none mode)
setup_required
boolean
true if admin password not configured (local mode only)
authenticated
boolean
true if valid session cookie present

Get Session Details

curl https://analytics.example.com/api/auth/session \
  -H "Cookie: spk_session=eyJhbGc..."
Response (200 OK):
{
  "data": {
    "authenticated": true,
    "mode": "local",
    "expires_at": "2026-03-10T12:00:00Z"
  }
}
Error (401 Unauthorized):
{
  "error": {
    "code": "unauthorized",
    "message": "Authentication required"
  }
}

Logout

curl -X POST https://analytics.example.com/api/auth/logout \
  -H "Cookie: spk_session=eyJhbGc..."
Response (200 OK):
{
  "data": {
    "ok": true
  }
}
Cookie cleared:
Set-Cookie: spk_session=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0; Secure

Rate Limiting (Login)

Protection: 5 failed login attempts per 15 minutes per IP address Response (429 Too Many Requests):
{
  "error": {
    "code": "rate_limited",
    "message": "Too many failed login attempts"
  }
}
Headers:
Retry-After: 900
Wait 900 seconds (15 minutes) before retrying.

Password Management

Setup Password (Local Mode Only)

First-run only. Returns 410 Gone if already configured.
curl -X POST https://analytics.example.com/api/auth/setup \
  -H "Content-Type: application/json" \
  -d '{
    "password": "your-secure-password"
  }'
Password requirements:
  • Minimum 12 characters
  • Must contain: uppercase, lowercase, number, special character
Response (201 Created):
{
  "data": {
    "ok": true
  }
}

Change Password (Local Mode Only)

Requires authentication. Invalidates all existing sessions.
curl -X PUT https://analytics.example.com/api/auth/password \
  -H "Content-Type: application/json" \
  -H "Cookie: spk_session=eyJhbGc..." \
  -d '{
    "current_password": "old-password",
    "new_password": "new-secure-password"
  }'
Response (200 OK):
{
  "data": {
    "ok": true
  }
}
Side effects:
  • JWT secret rotated (new 64-char hex string)
  • All sessions invalidated (users must re-login)

Argon2id Parameters

Password hashing uses Argon2id with configurable memory cost:
SPARKLYTICS_ARGON2_MEMORY_KB=65536  # 64 MB (default)
Fixed parameters:
  • Time cost: 3 iterations
  • Parallelism: 1 thread
  • Output length: 32 bytes

API Key Authentication

API keys provide programmatic access to query endpoints without session cookies.

API Key Format

API keys use mode-aware prefixes:

Self-Hosted Mode

spk_selfhosted_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
Structure:
  • Prefix: spk_selfhosted_
  • Random part: 32 hex characters
  • Total length: 47 characters

Cloud Mode

spk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
Structure:
  • Prefix: spk_live_
  • Random part: 32 hex characters
  • Total length: 40 characters

Storage

API keys are hashed before storage:
  • Hash algorithm: SHA-256
  • Stored fields:
    • key_hash - Full SHA-256 hash (64 hex chars)
    • key_prefix - First 20 characters of raw key (for display)
    • name - User-provided label
    • created_at - Timestamp
The raw API key is shown only once during creation. Store it securely — you cannot retrieve it later.

Create API Key

Requires session authentication.
curl -X POST https://analytics.example.com/api/auth/keys \
  -H "Content-Type: application/json" \
  -H "Cookie: spk_session=eyJhbGc..." \
  -d '{
    "name": "Production Server"
  }'
Response (201 Created):
{
  "data": {
    "id": "key_a1b2c3d4e5",
    "name": "Production Server",
    "key": "spk_selfhosted_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
    "prefix": "spk_selfhosted_a1b2",
    "created_at": "2026-03-03T12:00:00Z"
  }
}
id
string
Key identifier (format: key_ + 10 alphanumeric chars)
key
string
Full API key - Save this immediately, you cannot retrieve it later
prefix
string
Display-safe prefix (first 20 characters)

List API Keys

Requires session authentication.
curl "https://analytics.example.com/api/auth/keys?limit=20&offset=0" \
  -H "Cookie: spk_session=eyJhbGc..."
Response (200 OK):
{
  "data": [
    {
      "id": "key_a1b2c3d4e5",
      "name": "Production Server",
      "prefix": "spk_selfhosted_a1b2",
      "created_at": "2026-03-03T12:00:00Z"
    }
  ],
  "pagination": {
    "total": 1,
    "limit": 20,
    "offset": 0,
    "has_more": false
  }
}
limit
number
default:"20"
Number of keys to return (1-100)
offset
number
default:"0"
Pagination offset

Revoke API Key

Requires session authentication.
curl -X DELETE https://analytics.example.com/api/auth/keys/key_a1b2c3d4e5 \
  -H "Cookie: spk_session=eyJhbGc..."
Response (204 No Content) No response body. Key is permanently deleted. Error (404 Not Found):
{
  "error": {
    "code": "not_found",
    "message": "API key not found"
  }
}

Using API Keys

Send API keys in the Authorization header with Bearer scheme:
curl https://analytics.example.com/api/websites/site_abc123/stats \
  -H "Authorization: Bearer spk_selfhosted_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
Supported endpoints:
  • GET /api/websites - List websites
  • GET /api/websites/:id/stats - Website statistics
  • GET /api/websites/:id/pageviews - Pageview breakdown
  • GET /api/websites/:id/events - Event log
  • All other query endpoints (sessions, funnels, retention, etc.)
Not supported:
  • Event collection (POST /api/collect is unauthenticated)
  • Auth management endpoints (require session cookies)
  • Website creation/deletion (require session cookies)

Event Collection (Unauthenticated)

The POST /api/collect endpoint does not require authentication. Why?
  • Client-side JavaScript must be able to send events
  • CORS restrictions prevent cookie-based auth from all origins
  • Rate limiting (60 req/min per IP) prevents abuse
Validation:
  • Unknown website_id values are rejected with 404 Not Found
  • Events for deleted websites are rejected
Example:
curl -X POST https://analytics.example.com/api/collect \
  -H "Content-Type: application/json" \
  -d '{
    "website_id": "site_abc123",
    "event_type": "pageview",
    "url": "https://example.com/pricing"
  }'
Response (202 Accepted):
{
  "ok": true
}
See API Overview for full event schema and enrichment details.

Security Best Practices

HTTPS Only (Production)

Always deploy with HTTPS in production:
SPARKLYTICS_HTTPS=true  # Default
This enables the Secure cookie flag, preventing session cookies from being sent over plain HTTP. Local development only:
SPARKLYTICS_HTTPS=false  # Disables Secure flag for localhost

Reverse Proxy Recommendations

When deploying behind a reverse proxy (Nginx, Caddy, Traefik):
  1. Terminate TLS at the proxy (use automatic certificates with Caddy)
  2. Configure trusted proxies:
SPARKLYTICS_TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12
  1. Ensure proxy forwards client IP:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
See Reverse Proxy Setup for full configuration.

API Key Rotation

Best practices:
  • Rotate API keys every 90 days
  • Use separate keys for different environments (production, staging)
  • Revoke compromised keys immediately
  • Store keys in environment variables or secrets manager (never commit to git)

Password Requirements

Enforced in local mode setup:
  • Minimum 12 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one number
  • At least one special character
Recommendation: Use a password manager to generate strong passwords (16+ characters).

Multi-Tenancy (Cloud Mode)

Cloud deployments use Clerk for authentication:
  • Organizations map to tenant_id
  • Org-scoped JWT claims enforce data isolation
  • PostgreSQL stores user/org metadata
  • ClickHouse analytics tables include tenant_id column
See Cloud Deployment Guide (private repo) for details.
Self-hosted mode always stores tenant_id as NULL (single-tenant).

Build docs developers (and LLMs) love