Pular para o conteúdo principal

Authentication

Data Stream uses API Key authentication to protect endpoints and enable rate limiting per client.

Quick Start
# Create your first API key
task apikey:create -- -name "My App" -admin

# Use in requests
curl -H "X-API-Key: sk_live_xxxxx" https://datastream.hypetech.games/api/games

Overview

FeatureDescription
Key TypesPrivate (sk_*), Public (pk_*), Browser-bound
Token TypesShort-lived tokens (tok_*) with 15s TTL
Rate LimitingConfigurable per key (default: 1000 req/min)
Browser SecurityFingerprintJS + Origin validation + auto-refresh

Token Types

Data Stream uses three types of credentials:

PrefixTypeTTLUse Case
sk_live_*Private KeyPermanentServer-side only
pk_live_*Public KeyPermanentBrowser with Origin validation
tok_*Short-lived Token15 secondsREST/WebSocket browser sessions

Private Keys (sk_live_*)

Private keys are for server-side use only. Never expose in browser code.

sk_live_24c43125d90785dc51d50494b8f61e4c670c1913d4e89ce9f4d5deac30aa4735

Characteristics:

  • 64 character hex string after prefix
  • No Origin restrictions
  • Higher rate limit (1000 req/min default)
  • Full scope access when admin

Use for:

  • Backend services
  • Server-to-server communication
  • Admin operations
  • CLI tools

Public Keys (pk_live_*)

Public keys can be embedded in browser applications with Origin validation.

pk_live_a1b2c3d4e5f6789012345678901234567890123456789012345678901234

Characteristics:

  • Requires allowed_origins configuration
  • Lower rate limit (100 req/min default)
  • Origin header validated on every request
  • Can be combined with browser fingerprint for extra security

Use for:

  • Browser dashboards
  • Client-side applications
  • Demo pages

Short-lived Tokens (tok_*)

Tokens are temporary credentials for browser sessions.

tok_1b0f96fcb66fce576aeb015f55097323

Characteristics:

  • 15 second TTL (prevents curl replay attacks)
  • Generated from pk_* or sk_* keys
  • Inherits origin restrictions from parent key
  • Used for REST API and WebSocket connections
Token TTL

Tokens expire in 15 seconds. Implement auto-refresh on 401 responses.

Browser Authentication Flow

For browser applications, Data Stream provides a secure authentication flow using FingerprintJS for device identification.

How It Works

Browser Authentication Flow

Implementation

// 1. Initialize FingerprintJS
const fpPromise = import('https://openfpcdn.io/fingerprintjs/v4')
.then(FingerprintJS => FingerprintJS.load());

// 2. Get visitor ID
async function getVisitorId() {
const fp = await fpPromise;
const result = await fp.get();
return result.visitorId;
}

// 3. Get browser-bound key
async function getBrowserKey(visitorId) {
const res = await fetch('/api/auth/browser-key', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
visitor_id: visitorId,
bound_domain: window.location.hostname
})
});
const data = await res.json();
return data.key; // pk_live_*
}

// 4. Get short-lived token
async function getToken(apiKey) {
const res = await fetch('/api/auth/token', {
method: 'POST',
headers: { 'X-API-Key': apiKey }
});
const data = await res.json();
return data.token; // tok_*
}

// 5. Auto-refresh on 401
async function authFetch(url, options = {}) {
options.headers = { ...options.headers, 'X-API-Key': state.token };
let res = await fetch(url, options);

if (res.status === 401) {
console.log('Token expired, refreshing...');
state.token = await getToken(state.apiKey);
options.headers['X-API-Key'] = state.token;
res = await fetch(url, options);
}

return res;
}

// Complete initialization
async function initBrowserAuth() {
const visitorId = await getVisitorId();
const apiKey = await getBrowserKey(visitorId);
const token = await getToken(apiKey);

return { visitorId, apiKey, token };
}

Security Features

FeatureDescription
Browser FingerprintDevice identification via FingerprintJS
Origin BindingToken bound to requesting Origin header
Domain BindingKey tied to specific domain
Short TTL15s token lifetime prevents replay attacks
Auto-refreshTransparent token renewal on 401

Why 15 Second TTL?

The short token TTL provides several security benefits:

  1. Prevents curl replay: Tokens copied from DevTools expire before they can be used
  2. Limits exposure: Stolen tokens are only valid briefly
  3. Browser-friendly: Auto-refresh is transparent to the user
  4. Origin bound: Tokens validate the requesting origin
# This will fail - token expires in 15s
curl -H "X-API-Key: tok_xxxxx" https://api/games
# Error: Token expired

# Browser auto-refreshes seamlessly
fetch('/api/games'); // Works via authFetch()

API Key Types

Private Keys (Server-Side)

Private keys start with sk_live_ and should never be exposed to browsers.

# Create private key
task apikey:create -- -name "Backend Service" -scopes "read:rounds,stream:*"

Public Keys (Browser)

Public keys start with pk_live_ and can be used in browser applications with origin restrictions.

# Create public key with origin restriction
task apikey:create -- -name "Dashboard" -public -origins "https://dashboard.example.com"
aviso

Public keys require origin validation. Configure allowed origins when creating the key.

Creating API Keys

Using Taskfile

# Create admin key (all permissions)
task apikey:create -- -name "Admin Key" -admin

# Create limited key with specific scopes
task apikey:create -- -name "Mobile App" -scopes "read:rounds,stream:game" -rate-limit 500

# Create public key for browser dashboards
task apikey:create -- -name "Dashboard" -public -origins "https://app.example.com"

# List all keys
task apikey:list

# Revoke a key
task apikey:revoke -- -id <key-id>

# Delete a key
task apikey:delete -- -id <key-id>

Using REST API (Admin Only)

# Create key via API (requires admin key)
curl -X POST https://datastream.hypetech.games/api/auth/keys \
-H "X-API-Key: sk_live_admin_key" \
-H "Content-Type: application/json" \
-d '{
"name": "New App Key",
"scopes": ["read:rounds", "stream:game"],
"rate_limit": 500
}'

# List keys
curl -H "X-API-Key: sk_live_admin_key" \
https://datastream.hypetech.games/api/auth/keys

Available Scopes

ScopeDescriptionEndpoints
read:roundsRead round data/api/latest/*, /api/history/*
read:gamesRead game list/api/games
stream:*Stream all protocols/ws/*, /sse/*, gRPC streams
stream:gameStream specific game/ws/:game, /sse/:game
adminFull admin access/api/auth/keys/*

Using API Keys

REST API

Include the key in request headers:

# Using X-API-Key header (recommended)
curl -H "X-API-Key: sk_live_xxxxx" \
https://datastream.hypetech.games/api/games

# Using Authorization header
curl -H "Authorization: Bearer sk_live_xxxxx" \
https://datastream.hypetech.games/api/latest/crash

WebSocket

WebSocket requires a short-lived token (15 seconds) obtained via REST API:

// Step 1: Get token using API key
const tokenResponse = await fetch('/api/auth/token', {
method: 'POST',
headers: { 'X-API-Key': 'pk_live_xxxxx' }
});
const { token } = await tokenResponse.json();

// Step 2: Connect with token via Sec-WebSocket-Protocol
const ws = new WebSocket('wss://datastream.hypetech.games/ws/crash', [token]);

ws.onmessage = (e) => {
console.log(JSON.parse(e.data));
};

// Step 3: Reconnect on close with new token
ws.onclose = async () => {
const newToken = await getToken();
const newWs = new WebSocket('wss://datastream.hypetech.games/ws/crash', [newToken]);
};

Token format: tok_xxxxxxxxxxxxxxxxxxxxxxxx

observação

Tokens expire in 15 seconds. Get a new token for each connection or reconnection.

Server-Sent Events (SSE)

SSE uses a token in the query parameter (headers not supported by EventSource):

// Get token first
const token = await getToken();

// Connect with token
const sse = new EventSource(`/sse/crash?token=${encodeURIComponent(token)}`);

sse.addEventListener('message', (e) => {
console.log(JSON.parse(e.data));
});

// Reconnect on error with new token
sse.onerror = async () => {
sse.close();
const newToken = await getToken();
// Reconnect with new token...
};

gRPC

Include the key in metadata:

# Using grpcurl
grpcurl -plaintext \
-H "x-api-key: sk_live_xxxxx" \
datastream.hypetech.games:50051 \
datastream.DataStream/ListGames

# Or using authorization header
grpcurl -plaintext \
-H "authorization: Bearer sk_live_xxxxx" \
-d '{"game_slug":"crash"}' \
datastream.hypetech.games:50051 \
datastream.DataStream/StreamByGame

Go client:

import "google.golang.org/grpc/metadata"

// Create context with API key
ctx := metadata.AppendToOutgoingContext(context.Background(),
"x-api-key", "sk_live_xxxxx")

// Make call with authenticated context
stream, err := client.StreamByGame(ctx, &pb.StreamRequest{
GameSlug: "crash",
})

Auth Endpoints

EndpointMethodAuthDescription
/api/auth/scopesGETPublicList available scopes
/api/auth/tokenPOSTRequiredGet short-lived token (15s)
/api/auth/browser-keyPOSTPublicGet browser-bound key
/api/auth/keysGETAdminList all API keys
/api/auth/keysPOSTAdminCreate new API key
/api/auth/keys/:id/revokePOSTAdminRevoke an API key
/api/auth/keys/:idDELETEAdminDelete an API key

Get Browser Key

curl -X POST https://datastream.hypetech.games/api/auth/browser-key \
-H "Content-Type: application/json" \
-d '{
"visitor_id": "abc123fingerprint",
"bound_domain": "dashboard.example.com"
}'

Response:

{
"success": true,
"key": "pk_live_a1b2c3d4...",
"expires_at": 0,
"new": true
}

Get Token

curl -X POST https://datastream.hypetech.games/api/auth/token \
-H "X-API-Key: pk_live_xxxxx"

Response:

{
"success": true,
"token": "tok_1b0f96fcb66fce576aeb015f55097323",
"expires_in": 15,
"usage": "Use this token for REST API (X-API-Key header) or WebSocket (Sec-WebSocket-Protocol header)"
}

List Scopes

curl https://datastream.hypetech.games/api/auth/scopes

Response:

{
"scopes": [
{"name": "read:rounds", "description": "Read round data"},
{"name": "read:games", "description": "Read game list"},
{"name": "stream:*", "description": "Stream all data"},
{"name": "stream:game", "description": "Stream specific game"},
{"name": "admin", "description": "Admin access"}
]
}

Rate Limiting

Each API key has a configurable rate limit:

Key TypeDefault Rate Limit
Private (sk_*)1000 req/min
Public (pk_*)100 req/min

Rate limit headers:

X-Ratelimit-Limit: 1000
X-Ratelimit-Remaining: 997
X-Ratelimit-Reset: 45

Rate limit exceeded (HTTP 429):

{
"error": "Too many requests, please slow down",
"success": false
}

Environment Variables

VariableDefaultDescription
AUTH_REQUIRE_APIfalseRequire API key for REST endpoints
AUTH_REQUIRE_WSfalseRequire token for WebSocket
AUTH_REQUIRE_SSEfalseRequire API key for SSE
AUTH_REQUIRE_GRPCtrueRequire auth for gRPC

Complete Browser Example

class DataStreamBrowserClient {
constructor() {
this.apiKey = null;
this.token = null;
this.visitorId = null;
}

// Initialize with FingerprintJS
async init() {
// Load FingerprintJS
const FingerprintJS = await import('https://openfpcdn.io/fingerprintjs/v4')
.then(m => m.load());
const fp = await FingerprintJS.get();
this.visitorId = fp.visitorId;

// Get browser-bound key
const keyRes = await fetch('/api/auth/browser-key', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
visitor_id: this.visitorId,
bound_domain: window.location.hostname
})
});
const keyData = await keyRes.json();
this.apiKey = keyData.key;

// Get initial token
await this.refreshToken();
return this;
}

// Refresh token
async refreshToken() {
const res = await fetch('/api/auth/token', {
method: 'POST',
headers: { 'X-API-Key': this.apiKey }
});
const data = await res.json();
this.token = data.token;
return this.token;
}

// Authenticated fetch with auto-refresh
async fetch(url, options = {}) {
options.headers = { ...options.headers, 'X-API-Key': this.token };
let res = await fetch(url, options);

if (res.status === 401) {
await this.refreshToken();
options.headers['X-API-Key'] = this.token;
res = await fetch(url, options);
}

return res;
}

// Connect WebSocket
async connectWebSocket(game) {
const token = await this.refreshToken();
const ws = new WebSocket(
`wss://${window.location.host}/ws/${game}`,
[token]
);

ws.onclose = () => {
setTimeout(() => this.connectWebSocket(game), 1000);
};

return ws;
}

// Connect SSE
async connectSSE(game) {
const token = await this.refreshToken();
return new EventSource(`/sse/${game}?token=${encodeURIComponent(token)}`);
}
}

// Usage
const client = new DataStreamBrowserClient();
await client.init();

// REST with auto-refresh
const games = await client.fetch('/api/games').then(r => r.json());

// WebSocket with auto-reconnect
const ws = await client.connectWebSocket('crash');
ws.onmessage = (e) => console.log(JSON.parse(e.data));

// SSE
const sse = await client.connectSSE('crash');
sse.onmessage = (e) => console.log(JSON.parse(e.data));

Security Best Practices

  1. Never expose private keys - Use public keys (pk_*) for browser apps
  2. Use browser authentication - FingerprintJS + short-lived tokens
  3. Implement auto-refresh - Handle 401 responses transparently
  4. Rotate keys regularly - Revoke and recreate keys periodically
  5. Use minimal scopes - Only grant necessary permissions
  6. Monitor usage - Check last_used timestamp for suspicious activity
  7. Set appropriate rate limits - Protect against abuse
  8. Use HTTPS - Always use TLS in production