Authentication
Secure your PRIV integration with API keys, JWT tokens, and proper authentication patterns.
Overview
PRIV uses a multi-layered authentication system to secure different types of interactions:
API Keys
For SDK and server-to-server communication. Scoped by key type.
JWT Tokens
For authenticated user sessions in the dashboard and mobile apps.
API Key Authentication
Key Types
| Type | Prefix | Visibility | Use Case |
|---|---|---|---|
| Publishable | pk_live_ | Public | Client-side SDK |
| Secret | sk_live_ | Private | Server-side API |
| Test | pk_test_ / sk_test_ | Dev only | Testing |
Creating API Keys
- Navigate to your PRIV Dashboard
- Go to Settings > API Keys
- Click Create New Key
- Select key type and permissions
- Copy the key immediately (shown only once)
Key Permissions
When creating a secret key, you can scope its permissions:
| Permission | Description |
|---|---|
events:write | Send events via API |
events:read | Query event data |
users:read | Read user profiles |
users:write | Update user data |
analytics:read | Access analytics |
billing:manage | Manage subscriptions |
// Example: Creating a key with limited permissions
// Via Dashboard: Settings > API Keys > Create
// Select only: events:write, events:readUsing API Keys
SDK Integration (Publishable Key)
import { Priv } from '@priv/sdk'
const priv = new Priv({
apiKey: process.env.NEXT_PUBLIC_PRIV_KEY!, // pk_live_xxx
})
// Safe to use in browser - limited to event tracking
priv.track('page_view')Server-Side API (Secret Key)
// Server-side only - never expose in client code
const response = await fetch('https://api.priv.io/v1/events/query', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PRIV_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'SELECT * FROM events WHERE timestamp > now() - interval 1 day',
}),
})API Key Hashing
PRIV never stores API keys in plaintext. Keys are hashed using SHA-256:
// How PRIV hashes your API key (for reference)
async function hashApiKey(key: string): Promise<string> {
const encoder = new TextEncoder()
const data = encoder.encode(key)
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
const hashArray = Array.from(new Uint8Array(hashBuffer))
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
}JWT Authentication
For user sessions in the PRIV dashboard and mobile apps, we use JWT tokens via Supabase Auth.
Token Structure
interface PrivJWT {
// Standard claims
sub: string // User ID
iat: number // Issued at
exp: number // Expiration
// Custom claims
email: string
wallet_address?: string
role: 'user' | 'admin'
tier: 'free' | 'pro' | 'enterprise'
}Authentication Flow
Client-Side Token Usage
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
// Login
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'secure-password',
})
// Access token is automatically included in subsequent requests
const { data: profile } = await supabase
.from('profiles')
.select('*')
.single()Server-Side Token Verification
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function getUser() {
const cookieStore = await cookies()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name) {
return cookieStore.get(name)?.value
},
},
}
)
const { data: { user }, error } = await supabase.auth.getUser()
if (error || !user) {
return null
}
return user
}Web3 Wallet Authentication
PRIV supports wallet-based authentication using SIWE (Sign-In with Ethereum).
Flow
Implementation
import { useAccount, useSignMessage } from 'wagmi'
import { SiweMessage } from 'siwe'
function WalletLogin() {
const { address } = useAccount()
const { signMessageAsync } = useSignMessage()
async function handleLogin() {
// 1. Get nonce from server
const nonceRes = await fetch('/api/auth/nonce')
const { nonce } = await nonceRes.json()
// 2. Create SIWE message
const message = new SiweMessage({
domain: window.location.host,
address,
statement: 'Sign in to PRIV Protocol',
uri: window.location.origin,
version: '1',
chainId: 8453, // Base
nonce,
})
// 3. Sign message
const signature = await signMessageAsync({
message: message.prepareMessage(),
})
// 4. Verify on server
const verifyRes = await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, signature }),
})
const { token } = await verifyRes.json()
// Store token and redirect
}
return (
<button onClick={handleLogin}>
Sign in with Wallet
</button>
)
}Security Best Practices
API Key Security
Never Expose Secret Keys
Secret keys (sk_) should never appear in client-side code or be committed to version control.
Use Environment Variables
Store all keys in environment variables, never hardcode them.
# .env.local (never commit this file)
NEXT_PUBLIC_PRIV_KEY=pk_live_xxxxxxxxxxxxx
PRIV_SECRET_KEY=sk_live_xxxxxxxxxxxxx
# .env.example (safe to commit)
NEXT_PUBLIC_PRIV_KEY=pk_live_your_key_here
PRIV_SECRET_KEY=sk_live_your_key_hereKey Rotation
Rotate your API keys periodically:
- Create a new key in the dashboard
- Update your environment variables
- Deploy the update
- Revoke the old key
// Verify your integration after key rotation
const priv = new Priv({
apiKey: process.env.NEXT_PUBLIC_PRIV_KEY!,
})
// Check connection
const healthy = await priv.healthCheck()
console.log('SDK connected:', healthy)Rate Limiting
API endpoints are rate limited per API key:
| Endpoint Type | Limit |
|---|---|
| Event ingestion | 1000 req/min |
| Analytics queries | 100 req/min |
| User management | 60 req/min |
When rate limited, you'll receive a 429 Too Many Requests response:
// Handle rate limiting gracefully
try {
await priv.track('event')
} catch (error) {
if (error.status === 429) {
// Implement exponential backoff
await delay(Math.pow(2, retryCount) * 1000)
// Retry
}
}Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
401 Unauthorized | Invalid or expired API key | Check key is correct, not revoked |
403 Forbidden | Key lacks required permission | Create key with needed permissions |
429 Rate Limited | Too many requests | Implement backoff, reduce frequency |
| JWT expired | Token not refreshed | Enable auto-refresh in SDK |
Debugging Authentication
// Enable debug mode to see auth details
const priv = new Priv({
apiKey: process.env.NEXT_PUBLIC_PRIV_KEY!,
debug: true,
})
// Console will show:
// [PRIV] Authenticating with key: pk_live_xxx...xxx
// [PRIV] Auth successful, scopes: events:write