Authentication & Authorization

Comprehensive guide to Prism's API key authentication, rate limiting, and quota management.

Table of Contents


Overview

Prism provides a complete authentication and authorization system with:

  • API Key Authentication: SHA-256 hashed keys with SQLite storage

  • Rate Limiting: Token bucket algorithm with configurable limits

  • Method Permissions: Restrict access to specific RPC methods

  • Daily Quotas: Request count limits with automatic reset

  • In-Memory Caching: 60-second cache for authentication checks

Architecture

┌──────────────┐
│  HTTP        │
│  Request     │
└──────┬───────┘


┌────────────────────────────────────────┐
│      API Key Middleware                │
│                                        │
│  1. Extract API key (header/query)     │
│  2. Check in-memory cache (60s TTL)    │
│  3. Validate against SQLite DB         │
│  4. Check quotas & expiration          │
│  5. Verify method permissions          │
└──────┬─────────────────────────────────┘

       ├─ Valid ──────────► Process Request

       └─ Invalid ────────► 401 Unauthorized

API Key Management

Enabling Authentication

[auth]
enabled = true
database_url = "sqlite://db/auth.db"

When enabled, all requests require a valid API key.

Creating API Keys

Use the CLI to create and manage keys:

cargo run --bin cli -- auth create \
  --name "production-api" \
  --description "Main production service" \
  --rate-limit 100 \
  --refill-rate 10 \
  --daily-limit 100000 \
  --expires-in-days 365 \
  --methods "eth_getLogs,eth_getBlockByNumber,eth_getTransactionReceipt"

Parameters:

Parameter
Description
Example

--name

Unique identifier for the key

production-api

--description

Human-readable description

Main production service

--rate-limit

Max tokens in bucket

100

--refill-rate

Tokens added per second

10

--daily-limit

Max requests per day

100000

--expires-in-days

Days until expiration

365

--methods

Comma-separated allowed methods

eth_getLogs,eth_blockNumber

Output:

API key created successfully!

Name: production-api
API Key: rpc_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6
Description: Main production service

Rate Limit: 100 requests/sec (refill: 10/sec)
Daily Limit: 100,000 requests
Expires: 2025-12-03

Allowed Methods:
  - eth_getLogs
  - eth_getBlockByNumber
  - eth_getTransactionReceipt

IMPORTANT: Store this API key securely. It will not be shown again.

Listing API Keys

cargo run --bin cli -- auth list

Output:

API Keys:

1. production-api
   Created: 2024-12-03
   Expires: 2025-12-03
   Status: Active
   Rate Limit: 100/sec
   Daily Limit: 100,000
   Methods: eth_getLogs, eth_getBlockByNumber, eth_getTransactionReceipt

2. development-key
   Created: 2024-12-01
   Expires: 2024-12-31
   Status: Active
   Rate Limit: 10/sec
   Daily Limit: 10,000
   Methods: All

3. expired-key
   Created: 2024-01-01
   Expires: 2024-06-01
   Status: Expired
   Rate Limit: 50/sec
   Daily Limit: 50,000

Revoking API Keys

# Revoke by name
cargo run --bin cli -- auth revoke --name "production-api"

# Revoke by ID
cargo run --bin cli -- auth revoke --id 123

Using API Keys

Include the API key in requests:

Method 1: Header (Recommended)

curl -X POST http://localhost:3030/ \
  -H "Content-Type: application/json" \
  -H "X-API-Key: rpc_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

Method 2: Query Parameter

curl -X POST "http://localhost:3030/?api_key=rpc_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

API Key Format

API keys follow the format: rpc_<32-alphanumeric-characters>

  • Prefix: rpc_

  • Total Length: 36 characters

  • Random Portion: 32 alphanumeric characters (A-Z, a-z, 0-9)

Example: rpc_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6

Storage: Keys are hashed using Argon2id before storage. A SHA-256 blind index enables O(1) database lookups. The original key is never stored.


Rate Limiting

Prism implements a token bucket rate limiter per API key.

How It Works

Token Bucket Algorithm:

  1. Each API key has a bucket with max_tokens

  2. Bucket refills at refill_rate tokens/second

  3. Each request consumes 1 token

  4. Request allowed if tokens ≥ 1

  5. Request rejected if tokens < 1

Configuration

cargo run --bin cli -- auth create \
  --name "rate-limited-key" \
  --rate-limit 100 \      # Bucket capacity: 100 tokens
  --refill-rate 10        # Refill: 10 tokens/second

Example:

  • Bucket size: 100 tokens

  • Refill rate: 10 tokens/second

  • Sustainable rate: 10 requests/second

  • Burst capacity: 100 requests (then throttled to 10/sec)

Rate Limit Behavior

Scenario: Burst then sustain

Time 0s:  100 tokens available
  → Send 100 requests (burst)
  → 0 tokens remaining

Time 1s:  10 tokens refilled
  → Send 10 requests
  → 0 tokens remaining

Time 2s:  10 tokens refilled
  → Send 10 requests
  → 0 tokens remaining

Result:

  • First second: 100 requests accepted

  • Subsequent seconds: 10 requests/second sustained

Rate Limit Exceeded Response

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32053,
    "message": "Rate limit exceeded",
    "data": "Retry after 1 second"
  },
  "id": 1
}

HTTP Status: 429 Too Many Requests

Rate Limit Headers

Prism includes rate limit information in response headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 85
X-RateLimit-Reset: 1638360000
Header
Description

X-RateLimit-Limit

Maximum requests per window

X-RateLimit-Remaining

Tokens remaining

X-RateLimit-Reset

Unix timestamp when limit resets


Method-Level Permissions

Restrict API keys to specific RPC methods.

Configuration

Allow specific methods:

cargo run --bin cli -- auth create \
  --name "logs-only-key" \
  --methods "eth_getLogs"

Allow multiple methods:

cargo run --bin cli -- auth create \
  --name "read-only-key" \
  --methods "eth_getLogs,eth_getBlockByNumber,eth_getTransactionReceipt,eth_blockNumber"

Allow all methods (omit --methods or use "all"):

cargo run --bin cli -- auth create \
  --name "admin-key" \
  --methods "all"

Permission Check

When a request arrives:

  1. Extract method from request

  2. Check if key has permission

  3. Allow or deny

Example:

# API key only has permission for eth_getLogs
X-API-Key: prism_logs_only_key

# This succeeds
{"method": "eth_getLogs", ...}

# This fails with 403 Forbidden
{"method": "eth_getBlockByNumber", ...}

Permission Denied Response

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32055,
    "message": "Method not allowed",
    "data": "API key does not have permission for method: eth_getBlockByNumber"
  },
  "id": 1
}

HTTP Status: 403 Forbidden

Use Cases

Read-only access:

--methods "eth_getLogs,eth_getBlockByNumber,eth_getTransactionReceipt,eth_blockNumber,eth_getBalance"

Logs-only access (for event indexers):

--methods "eth_getLogs,eth_blockNumber"

Admin access (all methods):

--methods "all"

Quota Management

Daily request quotas with automatic midnight reset.

Configuration

cargo run --bin cli -- auth create \
  --name "quota-limited-key" \
  --daily-limit 100000  # 100,000 requests per day

Quota Tracking

Quota State:

  • requests_made_today: Current request count

  • quota_reset_at: Midnight UTC when quota resets

  • daily_request_limit: Maximum allowed requests

Quota Check:

  1. Is current time > quota_reset_at?

    • YES: Reset requests_made_today to 0, update quota_reset_at to next midnight

  2. Is requests_made_today < daily_request_limit?

    • YES: Allow request, increment counter

    • NO: Deny request

Quota Exceeded Response

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32056,
    "message": "Quota exceeded",
    "data": "Daily limit of 100000 requests exceeded. Quota resets at 2024-12-04T00:00:00Z"
  },
  "id": 1
}

HTTP Status: 429 Too Many Requests

Quota Headers

X-Quota-Limit: 100000
X-Quota-Remaining: 45230
X-Quota-Reset: 2024-12-04T00:00:00Z

Security Best Practices

1. Secure API Key Storage

DON'T:

  • Store keys in version control

  • Hardcode keys in source code

  • Share keys between environments

DO:

  • Use environment variables

  • Use secret management systems (AWS Secrets Manager, HashiCorp Vault)

  • Rotate keys regularly

Example (Node.js):

// DON'T
const apiKey = "rpc_A1b2C3d4E5f6...";

// DO
const apiKey = process.env.PRISM_API_KEY;

2. Use Method Restrictions

Principle of least privilege: grant only necessary methods.

# Good: Restrict to needed methods
cargo run --bin cli -- auth create \
  --name "frontend-app" \
  --methods "eth_getLogs,eth_getBlockByNumber"

# Bad: All methods when not needed
cargo run --bin cli -- auth create \
  --name "frontend-app" \
  --methods "all"

3. Set Appropriate Rate Limits

Balance performance and cost:

# Conservative (low traffic)
--rate-limit 10 --refill-rate 1

# Moderate (typical app)
--rate-limit 100 --refill-rate 10

# Aggressive (high traffic)
--rate-limit 1000 --refill-rate 100

4. Monitor Usage

Regularly review API key usage:

# Check metrics
curl http://localhost:3030/metrics | grep auth

# Example metrics:
# rpc_auth_success_total{key_id="production-api"} 125000
# rpc_auth_failure_total{key_id="unknown"} 45
# rpc_auth_quota_exceeded_total{key_id="production-api"} 3

5. Rotate Keys Regularly

# Create new key
cargo run --bin cli -- auth create --name "production-api-v2" ...

# Update applications to use new key

# Revoke old key after transition
cargo run --bin cli -- auth revoke --name "production-api"

6. Use HTTPS in Production

DON'T:

# Sends API key in plaintext
http://prism.example.com/?api_key=prism_...

DO:

# Encrypted connection
https://prism.example.com/
Header: X-API-Key: prism_...

7. Implement Retry Logic

Handle rate limit and quota errors gracefully:

async function makeRequest(payload, retries = 3) {
  for (let i = 0; i < retries; i++) {
    const response = await fetch('https://prism.example.com/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': process.env.PRISM_API_KEY
      },
      body: JSON.stringify(payload)
    });

    if (response.status === 429) {
      // Rate limited, exponential backoff
      const delay = Math.pow(2, i) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
      continue;
    }

    return await response.json();
  }

  throw new Error('Max retries exceeded');
}

Database Schema

API keys are stored in SQLite with the following schema:

CREATE TABLE api_keys (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  key_hash TEXT NOT NULL,           -- Argon2id hash (PHC format)
  blind_index TEXT NOT NULL UNIQUE, -- SHA-256 hash for O(1) lookup
  name TEXT NOT NULL UNIQUE,
  description TEXT,
  is_active BOOLEAN NOT NULL DEFAULT 1,
  created_at TEXT NOT NULL,         -- ISO 8601 datetime
  updated_at TEXT NOT NULL,         -- ISO 8601 datetime
  last_used_at TEXT,                -- ISO 8601 datetime
  expires_at TEXT,                  -- ISO 8601 datetime
  rate_limit_max_tokens INTEGER NOT NULL,
  rate_limit_refill_rate INTEGER NOT NULL,
  daily_request_limit INTEGER,
  daily_requests_used INTEGER DEFAULT 0,
  quota_reset_at TEXT               -- ISO 8601 datetime
);

CREATE TABLE method_permissions (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  api_key_id INTEGER NOT NULL,
  method_name TEXT NOT NULL,
  max_requests_per_day INTEGER,     -- Per-method daily limit (NULL = unlimited)
  requests_today INTEGER DEFAULT 0,
  FOREIGN KEY (api_key_id) REFERENCES api_keys(id)
);

CREATE INDEX idx_blind_index ON api_keys(blind_index);
CREATE INDEX idx_active_keys ON api_keys(is_active);
CREATE INDEX idx_method_permissions_api_key ON method_permissions(api_key_id);

Security Notes:

  • key_hash: Argon2id with memory=64MB, iterations=3, parallelism=4

  • blind_index: SHA-256 for timing-attack resistant O(1) lookups

  • The original API key is never stored


Metrics

Authentication metrics are exposed at /metrics:

# Authentication attempts
rpc_auth_success_total{key_id="production-api"} 125000
rpc_auth_failure_total{key_id="unknown"} 45

# Cache performance
rpc_auth_cache_hits_total 120000
rpc_auth_cache_misses_total 5045

# Rate limiting
rpc_rate_limit_allowed_total{key="production-api"} 124500
rpc_rate_limit_rejected_total{key="production-api"} 500

# Quotas
rpc_auth_quota_exceeded_total{key_id="production-api"} 3

# Method permissions
rpc_auth_method_denied_total{key_id="logs-only",method="eth_getBlockByNumber"} 12

Next: Learn about Monitoring for comprehensive observability.

Last updated