PRIV ProtocolPRIV Docs
Guides

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

TypePrefixVisibilityUse Case
Publishablepk_live_PublicClient-side SDK
Secretsk_live_PrivateServer-side API
Testpk_test_ / sk_test_Dev onlyTesting

Creating API Keys

  1. Navigate to your PRIV Dashboard
  2. Go to Settings > API Keys
  3. Click Create New Key
  4. Select key type and permissions
  5. Copy the key immediately (shown only once)

Key Permissions

When creating a secret key, you can scope its permissions:

PermissionDescription
events:writeSend events via API
events:readQuery event data
users:readRead user profiles
users:writeUpdate user data
analytics:readAccess analytics
billing:manageManage subscriptions
// Example: Creating a key with limited permissions
// Via Dashboard: Settings > API Keys > Create
// Select only: events:write, events:read

Using 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_here

Key Rotation

Rotate your API keys periodically:

  1. Create a new key in the dashboard
  2. Update your environment variables
  3. Deploy the update
  4. 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 TypeLimit
Event ingestion1000 req/min
Analytics queries100 req/min
User management60 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

IssueCauseSolution
401 UnauthorizedInvalid or expired API keyCheck key is correct, not revoked
403 ForbiddenKey lacks required permissionCreate key with needed permissions
429 Rate LimitedToo many requestsImplement backoff, reduce frequency
JWT expiredToken not refreshedEnable 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

Next Steps