API Authentication
API Authentication
Section titled “API Authentication”This guide covers authentication methods, security headers, and CSRF protection for the Bifrost REST APIs.
Authentication Methods
Section titled “Authentication Methods”Bearer Token Authentication
Section titled “Bearer Token Authentication”The primary authentication method uses a Bearer token in the HTTP Authorization header.
Configuration:
api: listen: ":7082" token: "your-secure-api-token" # Set to empty string to disable authRequest:
curl -H "Authorization: Bearer your-secure-api-token" \ http://localhost:7082/api/v1/statusQuery Parameter Token
Section titled “Query Parameter Token”For WebSocket connections where headers cannot be easily set, use the token query parameter.
# WebSocket connectionws://localhost:7082/api/v1/ws?token=your-secure-api-token
# HTTP request (fallback)curl "http://localhost:7082/api/v1/status?token=your-secure-api-token"No Authentication
Section titled “No Authentication”If no token is configured (empty string), all API endpoints are publicly accessible. This is suitable for:
- Development environments
- Trusted internal networks
- When authentication is handled externally (reverse proxy, VPN)
api: listen: ":7082" token: "" # Authentication disabledCSRF Protection
Section titled “CSRF Protection”Bifrost implements CSRF (Cross-Site Request Forgery) protection for all mutating requests.
How It Works
Section titled “How It Works”All POST, PUT, DELETE, and PATCH requests require the X-Requested-With header:
X-Requested-With: XMLHttpRequestThis header cannot be set by cross-origin requests without CORS approval, preventing CSRF attacks from malicious websites.
Example Request
Section titled “Example Request”curl -X POST \ -H "Authorization: Bearer your-token" \ -H "X-Requested-With: XMLHttpRequest" \ -H "Content-Type: application/json" \ -d '{"name": "my-route", "domains": ["*.example.com"], "backend": "direct"}' \ http://localhost:7082/api/v1/routesCSRF Error Response
Section titled “CSRF Error Response”If the X-Requested-With header is missing:
HTTP/1.1 403 ForbiddenContent-Type: text/plain
CSRF validation failed: missing X-Requested-With headerSecurity Headers
Section titled “Security Headers”All API responses include security headers to protect against common web vulnerabilities.
Headers Applied
Section titled “Headers Applied”| Header | Value | Purpose |
|---|---|---|
X-Content-Type-Options | nosniff | Prevent MIME type sniffing |
X-Frame-Options | DENY | Prevent clickjacking |
X-XSS-Protection | 1; mode=block | Legacy XSS protection |
Referrer-Policy | strict-origin-when-cross-origin | Control referrer information |
Content-Security-Policy | See below | Restrict resource loading |
Content Security Policy
Section titled “Content Security Policy”default-src 'self';script-src 'self' 'unsafe-inline';style-src 'self' 'unsafe-inline';font-src 'self';img-src 'self' data: https:;connect-src 'self' ws: wss:;frame-ancestors 'none'This CSP:
- Allows scripts and styles from the same origin
- Permits inline scripts/styles for React/Vite
- Allows WebSocket connections
- Blocks framing (clickjacking protection)
- Allows images from HTTPS sources
CORS Policy
Section titled “CORS Policy”The client API includes CORS (Cross-Origin Resource Sharing) support for local development.
Allowed Origins
Section titled “Allowed Origins”Requests are allowed from:
http://localhost:*https://localhost:*http://127.0.0.1:*https://127.0.0.1:*http://[::1]:*https://[::1]:*
CORS Headers
Section titled “CORS Headers”For allowed origins:
Access-Control-Allow-Origin: http://localhost:5173Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONSAccess-Control-Allow-Headers: Accept, Authorization, Content-Type, X-Requested-WithAccess-Control-Allow-Credentials: truePreflight Requests
Section titled “Preflight Requests”OPTIONS requests receive a 204 No Content response with CORS headers.
Token Security Best Practices
Section titled “Token Security Best Practices”Generating Secure Tokens
Section titled “Generating Secure Tokens”# Using OpenSSLopenssl rand -base64 32
# Using /dev/urandomhead -c 32 /dev/urandom | base64
# Using Gogo run -mod=mod github.com/google/uuid/cmd/uuidgen@latestToken Storage
Section titled “Token Storage”- Never commit tokens to version control
- Use environment variables or secrets management
- Rotate tokens periodically
- Use different tokens per environment
# config.yamlapi: token: ${BIFROST_API_TOKEN} # Environment variable# Set environment variableexport BIFROST_API_TOKEN="your-secure-token"Token Length
Section titled “Token Length”Recommended minimum token length: 32 characters
The token comparison uses constant-time comparison to prevent timing attacks.
Error Responses
Section titled “Error Responses”401 Unauthorized
Section titled “401 Unauthorized”Returned when:
- Token is missing
- Token is invalid
{ "error": "Unauthorized"}403 Forbidden
Section titled “403 Forbidden”Returned when:
- CSRF validation fails
CSRF validation failed: missing X-Requested-With headerMiddleware Stack
Section titled “Middleware Stack”Requests pass through these middleware layers in order:
- Request ID - Assigns unique ID for tracking
- Real IP - Extracts client IP (supports X-Forwarded-For)
- Logger - Logs HTTP requests
- Recoverer - Catches panics, returns 500
- Timeout - 30-second request timeout
- Security Headers - Adds security headers
- Auth Middleware - Validates token (if configured)
- CSRF Middleware - Validates X-Requested-With header
Client Code Examples
Section titled “Client Code Examples”JavaScript/TypeScript
Section titled “JavaScript/TypeScript”const API_BASE = 'http://localhost:7082';const TOKEN = 'your-api-token';
// GET request (read-only)async function getStatus() { const response = await fetch(`${API_BASE}/api/v1/status`, { headers: { 'Authorization': `Bearer ${TOKEN}`, }, }); return response.json();}
// POST request (requires CSRF header)async function addRoute(route: { name: string; domains: string[]; backend: string }) { const response = await fetch(`${API_BASE}/api/v1/routes`, { method: 'POST', headers: { 'Authorization': `Bearer ${TOKEN}`, 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', // Required for CSRF }, body: JSON.stringify(route), });
if (response.status === 401) { throw new Error('Unauthorized: Invalid or missing API token'); } if (response.status === 403) { throw new Error('Forbidden: CSRF validation failed'); }
return response.json();}Python
Section titled “Python”import requests
class BifrostClient: def __init__(self, base_url: str, token: str): self.base_url = base_url self.session = requests.Session() self.session.headers.update({ 'Authorization': f'Bearer {token}', 'X-Requested-With': 'XMLHttpRequest', })
def get_status(self) -> dict: response = self.session.get(f'{self.base_url}/api/v1/status') response.raise_for_status() return response.json()
def add_route(self, name: str, domains: list[str], backend: str) -> dict: response = self.session.post( f'{self.base_url}/api/v1/routes', json={'name': name, 'domains': domains, 'backend': backend} ) response.raise_for_status() return response.json()
# Usageclient = BifrostClient('http://localhost:7082', 'your-token')status = client.get_status()package main
import ( "bytes" "encoding/json" "fmt" "net/http")
type BifrostClient struct { BaseURL string Token string Client *http.Client}
func NewBifrostClient(baseURL, token string) *BifrostClient { return &BifrostClient{ BaseURL: baseURL, Token: token, Client: &http.Client{}, }}
func (c *BifrostClient) doRequest(method, path string, body interface{}) (*http.Response, error) { var bodyReader *bytes.Reader if body != nil { jsonBody, _ := json.Marshal(body) bodyReader = bytes.NewReader(jsonBody) }
req, _ := http.NewRequest(method, c.BaseURL+path, bodyReader) req.Header.Set("Authorization", "Bearer "+c.Token) req.Header.Set("Content-Type", "application/json")
// Add CSRF header for mutating requests if method != "GET" && method != "HEAD" { req.Header.Set("X-Requested-With", "XMLHttpRequest") }
return c.Client.Do(req)}
func (c *BifrostClient) GetStatus() (map[string]interface{}, error) { resp, err := c.doRequest("GET", "/api/v1/status", nil) if err != nil { return nil, err } defer resp.Body.Close()
if resp.StatusCode == 401 { return nil, fmt.Errorf("unauthorized: invalid or missing API token") }
var result map[string]interface{} json.NewDecoder(resp.Body).Decode(&result) return result, nil}
func (c *BifrostClient) AddRoute(name string, domains []string, backend string) error { route := map[string]interface{}{ "name": name, "domains": domains, "backend": backend, }
resp, err := c.doRequest("POST", "/api/v1/routes", route) if err != nil { return err } defer resp.Body.Close()
if resp.StatusCode == 401 { return fmt.Errorf("unauthorized") } if resp.StatusCode == 403 { return fmt.Errorf("CSRF validation failed") }
return nil}cURL Wrapper Script
Section titled “cURL Wrapper Script”#!/bin/bash# bifrost-api.sh - Wrapper for Bifrost API calls
BIFROST_URL="${BIFROST_URL:-http://localhost:7082}"BIFROST_TOKEN="${BIFROST_TOKEN:-}"
bifrost_get() { local path="$1" curl -s -H "Authorization: Bearer $BIFROST_TOKEN" \ "$BIFROST_URL$path"}
bifrost_post() { local path="$1" local data="$2" curl -s -X POST \ -H "Authorization: Bearer $BIFROST_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Requested-With: XMLHttpRequest" \ -d "$data" \ "$BIFROST_URL$path"}
bifrost_delete() { local path="$1" curl -s -X DELETE \ -H "Authorization: Bearer $BIFROST_TOKEN" \ -H "X-Requested-With: XMLHttpRequest" \ "$BIFROST_URL$path"}
# Usage examples:# bifrost_get /api/v1/status# bifrost_post /api/v1/routes '{"name":"test","domains":["*.test.com"],"backend":"direct"}'# bifrost_delete /api/v1/routes/testTroubleshooting
Section titled “Troubleshooting””Unauthorized” Error
Section titled “”Unauthorized” Error”- Verify token is correct
- Check
Authorizationheader format:Bearer <token> - Ensure no extra whitespace in token
- Verify token matches server config
”CSRF validation failed” Error
Section titled “”CSRF validation failed” Error”- Add
X-Requested-With: XMLHttpRequestheader - Ensure header is included for POST/PUT/DELETE/PATCH requests
- Check for proxy stripping custom headers
Connection Refused
Section titled “Connection Refused”- Verify server is running
- Check API listen address in config
- Ensure firewall allows connections
- Try with explicit IP:
http://127.0.0.1:7082instead oflocalhost