Player Key Authentication Implementation
August 12, 2025 · View on GitHub
Overview
This implementation introduces separate player keys for the SRT Live Server using the configured stream ID format (domain_player/app_player/playerkey), allowing you to control access to streams using player-specific keys that are validated through an API endpoint.
Features
1. Configuration Option
- New configuration:
player_key_auth_url - Purpose: Specifies the API endpoint to validate player keys
- Format: HTTP URL that accepts GET requests with
player_keyparameter
2. Configured Stream ID Format
Player connections use the configured domain and app names from the SLS configuration:
srt://host:port?streamid=<domain_player>/<app_player>/playerkey
When player key authentication is enabled:
domain_player= configured domain for players (e.g., "play")app_player= configured application name for players (e.g., "live")playerkey= the player key to validate
3. API Integration
When a player connects with format <domain_player>/<app_player>/playerkey, the server makes an HTTP GET request to:
<player_key_auth_url>?player_key=playerkey
Expected API response (JSON only):
- JSON format:
{"stream_id": "publish/live/actualstream"} - HTTP 200: Player key is valid, connect to the returned stream ID
- Non-200 status: Player key is invalid, reject connection
API Response
Required:
stream_id(string) — e.g.publish/live/actualstream
Optional:
max_players_per_stream(integer) — per-key player limit override;-1for unlimited. If provided, it overrides the app-levelmax_players_per_streamfor connections authenticated with this key and applies to the resolved stream.
Example successful response:
{
"stream_id": "publish/live/movie1",
"max_players_per_stream": 10
}
If omitted, the server uses the configured app-level limit.
Configuration Example
server {
listen_player 4000;
listen_publisher 4001;
domain_player play;
domain_publisher publish;
# Enable player key authentication
player_key_auth_url http://127.0.0.1:8000/sls/validate_player_key;
player_key_auth_timeout 2000; # HTTP timeout in milliseconds
player_key_cache_duration 60000; # Cache duration in milliseconds
# Security features
player_key_rate_limit_requests -1; # No rate limiting by default (-1 = unlimited)
player_key_rate_limit_window 60000; # Rate limit window in milliseconds
player_key_min_length 8; # Minimum player key length
player_key_max_length 64; # Maximum player key length
app {
app_player live;
app_publisher live;
allow publish all;
allow play all;
}
}
Usage Examples
Publisher Connection (unchanged)
ffmpeg -f ... -c ... "srt://host:4001?streamid=publish/live/mystream"
Player Connection with Player Key
ffplay "srt://host:4000?streamid=play/live/abc123"
In this example:
- The player provides stream ID
play/live/abc123(using configureddomain_player=playandapp_player=live) - Server extracts player key
abc123from the stream ID - Server calls:
http://127.0.0.1:8000/sls/validate_player_key?player_key=abc123 - API returns:
{"stream_id": "publish/live/mystream"} - Player is connected to the actual stream
mystream
Implementation Details
Files Modified
-
src/core/SLSListener.hpp
- Added
player_key_auth_urlconfiguration parameter - Added
m_player_key_auth_urlmember variable - Added
validate_player_key()method declaration - Added member variables for storing configured domains and apps
- Added
-
src/core/SLSListener.cpp
- Added
#include <nlohmann/json.hpp>for JSON parsing - Implemented
validate_player_key()method with HTTP client and JSON parsing - Added stream ID parsing using configured
domain_playerandapp_playervalues - Store configuration values in member variables during initialization
- Integrated player key validation into connection handling
- Added
-
src/sls.conf
- Added configuration examples showing usage with configured values
- Updated documentation for JSON-only API responses
Key Implementation Features
- Uses Configuration: Respects configured
domain_playerandapp_playervalues - Multiple Domains/Apps: Supports multiple configured player domains and apps
- JSON Only: API responses must be valid JSON with
stream_idfield. Optionally include per-key player limit viamax_players_per_stream. - Backward Compatibility: Only applies to configured player formats when auth URL is configured
- Stream ID Replacement: Validated stream ID replaces the original for processing
- Robust JSON Parsing: Uses nlohmann/json library with proper error handling
Performance Optimizations
1. Non-Blocking HTTP Requests
The implementation uses non-blocking HTTP requests with configurable timeouts to prevent blocking the listener thread:
- Timeout Control: Configurable
player_key_auth_timeout(default: 2000ms) - Non-Blocking Loop: Uses polling with microsecond sleeps to avoid busy waiting
- Graceful Timeouts: Proper cleanup on timeout with detailed error logging
2. Player Key Caching
To reduce API calls and improve responsiveness, the server caches successful validations:
- Configurable Duration:
player_key_cache_duration(default: 60000ms) - Automatic Expiry: Cache entries automatically expire after the configured duration
- Cache Cleanup: Expired entries are removed when accessed
- Memory Efficient: Uses STL map for fast lookups and automatic memory management
- Negative Caching: Failed validations are cached briefly to prevent repeated API abuse
3. Configuration Options
New configuration parameters for performance tuning:
player_key_auth_timeout 2000; # HTTP timeout in milliseconds
player_key_cache_duration 60000; # Cache duration in milliseconds
Recommended Settings by Use Case:
Standard Server (Default - no rate limiting):
player_key_rate_limit_requests -1; # Unlimited (default)
player_key_min_length 8;
player_key_max_length 64;
High-Security Server (Enable strict rate limiting):
player_key_rate_limit_requests 5; # Enable strict rate limiting
player_key_min_length 12;
player_key_max_length 32;
Low-Latency Server (Moderate rate limiting):
player_key_rate_limit_requests 15; # Enable moderate rate limiting
player_key_min_length 6;
player_key_max_length 32;
Security Features
1. Rate Limiting per IP Address
Prevents abuse by limiting validation requests per IP address:
- Configurable Limits:
player_key_rate_limit_requests(default: -1, unlimited) - Sliding Window:
player_key_rate_limit_window(default: 60000ms) - Automatic Cleanup: Expired rate limit entries are automatically removed
- Per-IP Tracking: Each client IP is tracked independently
- Disabled by Default: Set to -1 for unlimited requests (default behavior)
Configuration:
player_key_rate_limit_requests -1; # -1 = unlimited (default), >0 = max requests per window
player_key_rate_limit_window 60000; # Rate limit window in milliseconds
To enable rate limiting:
player_key_rate_limit_requests 10; # Enable: max 10 requests per IP per window
player_key_rate_limit_window 60000; # 60 second window
2. Player Key Format Validation
Input validation ensures player keys meet security requirements:
- Length Constraints: Configurable minimum and maximum length
- Character Validation: Regex-based format validation
- Fast Rejection: Invalid formats are rejected before API calls
- Secure Defaults: Alphanumeric, hyphens, and underscores only
Configuration:
player_key_min_length 8; # Minimum player key length
player_key_max_length 64; # Maximum player key length
# Valid pattern: ^[a-zA-Z0-9_-]{min,max}$
3. Negative Caching
Failed validations are cached to prevent repeated API abuse:
- Shorter Duration: Failed keys cached for 1/4 of normal cache time
- Prevents Brute Force: Reduces load on authentication API
- Automatic Expiry: Expired negative entries are cleaned up
- Security Logging: Failed attempts are logged for monitoring
4. Multi-Layer Security
The implementation provides defense in depth:
- Format Validation - Fast client-side checks
- Rate Limiting - Per-IP request throttling
- Negative Caching - Prevents repeated invalid attempts
- Timeout Protection - Prevents hanging connections
- Comprehensive Logging - Security event monitoring
Security Event Logging:
- Invalid format attempts
- Rate limit violations
- Failed authentication attempts
- Timeout events
- Cache hits/misses
Stream ID Flow
- Player connects:
srt://host:4000?streamid=play/live/playerkey123 - Server detects: Configured format with matching
domain_playerandapp_player - Extract key:
playerkey123from the third part - API call:
GET /validate_player_key?player_key=playerkey123 - API response:
{"stream_id": "publish/live/actualstream"} - Stream replacement: Original
play/live/playerkey123becomespublish/live/actualstream - Normal processing: Continue with standard SRT Live Server logic
Configuration Flexibility
The implementation works with any configured domain and app names:
# Example 1: Standard configuration
domain_player play;
app_player live;
# Player connects: srt://host:port?streamid=play/live/playerkey
# Example 2: Custom configuration
domain_player viewer;
app_player stream;
# Player connects: srt://host:port?streamid=viewer/stream/playerkey
# Example 3: Multiple domains
domain_player "play watch";
app_player live;
# Player can connect with either:
# srt://host:port?streamid=play/live/playerkey
# srt://host:port?streamid=watch/live/playerkey
API Endpoint Implementation Example
Here's a simple Python Flask API endpoint example:
from flask import Flask, request, jsonify
app = Flask(__name__)
# Mock database of player keys -> stream mappings
PLAYER_KEYS = {
"abc123": "publish/live/stream1",
"def456": "publish/live/stream2",
"xyz789": "publish/live/stream3"
}
@app.route('/sls/validate_player_key')
def validate_player_key():
player_key = request.args.get('player_key')
if not player_key:
return jsonify({"error": "Missing player_key parameter"}), 400
if player_key in PLAYER_KEYS:
stream_id = PLAYER_KEYS[player_key]
return jsonify({"stream_id": stream_id})
else:
return jsonify({"error": "Invalid player key"}), 403
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8000)
Error Handling
The implementation includes comprehensive error handling:
- Invalid JSON: If API response is not valid JSON, connection is rejected
- Missing stream_id: If JSON doesn't contain
stream_idfield, connection is rejected - Non-string stream_id: If
stream_idfield is not a string, connection is rejected - Invalid Player Key: If API returns non-200 status, connection is rejected
- API Unavailable: If HTTP request fails, connection is rejected
- Network Timeouts: Built-in timeout handling via existing HTTP client
- HTTP Request Timeout: Configurable timeout prevents blocking listener thread
- Cache Management: Automatic cleanup of expired cache entries
- Invalid Key Format: Player keys that don't match regex pattern are rejected
- Rate Limiting: Excessive requests from same IP are rejected
- Length Validation: Keys outside configured length range are rejected
- Negative Cache Hits: Previously failed keys are rejected from cache
Testing
To test the implementation:
- Set up an API endpoint that responds to player key validation requests
- Configure
player_key_auth_urlin your server configuration - Test publisher connections:
srt://host:port?streamid=publish/live/test(should work unchanged) - Test player connections with valid keys:
srt://host:port?streamid=play/live/validkey(should work) - Test player connections with invalid keys:
srt://host:port?streamid=play/live/invalidkey(should be rejected) - Test normal player connections:
srt://host:port?streamid=play/live/normalstream(should work if no auth URL configured)
Benefits
- Simple Format: Uses standard SRT stream ID format, no complex parameters
- Clean Integration: Player key is naturally part of the stream identifier
- Dynamic Mapping: Player keys can map to any actual stream via API
- Secure: Invalid keys are rejected at connection time
- Flexible: API can implement any authentication/authorization logic
- JSON Standard: Uses industry-standard JSON for API responses
Player Connection Limits
How Player Limits Work with Player Keys
The max_players_per_stream setting correctly applies to the resolved stream after player key validation, not to individual player keys. This ensures proper connection limiting regardless of how many different player keys resolve to the same stream.
Behavior:
- Multiple different player keys resolving to the same stream count toward the same limit
- Player limits are enforced on the actual stream name returned by the API
- Each unique resolved stream has its own independent player count
- If the API returns
max_players_per_stream, the server stores a per-stream cap for the resolved stream (with the key's cache expiry). This per-stream cap applies to all subsequent players on that resolved stream, regardless of which key they used, until it expires. If no per-stream cap exists, the app-level limit is used.
Example:
app {
max_players_per_stream 3; # Maximum 3 players per resolved stream
}
If the API responds for a key with { "stream_id": "publish/live/movie1", "max_players_per_stream": 10 }, then the per-stream cap for publish/live/movie1 becomes 10 (for the duration of the key's cache). All players to publish/live/movie1 are limited to 10 during that period, regardless of which key they used. When the cap expires, the server falls back to the app-level limit (3) unless refreshed by another validated key response.
Scenario:
# Player 1 connects
ffplay "srt://host:4000?streamid=play/live/key123"
# API returns: {"stream_id": "publish/live/movie1"}
# Player 2 connects
ffplay "srt://host:4000?streamid=play/live/key456"
# API returns: {"stream_id": "publish/live/movie1"} # Same stream!
# Player 3 connects
ffplay "srt://host:4000?streamid=play/live/key789"
# API returns: {"stream_id": "publish/live/movie1"} # Same stream!
# Player 4 tries to connect
ffplay "srt://host:4000?streamid=play/live/key999"
# API returns: {"stream_id": "publish/live/movie1"}
# CONNECTION REJECTED - stream limit (3) reached for "movie1"
Key Points:
- ✅ Correct Limiting: All 4 player keys count toward the same "movie1" stream limit
- ✅ Security: Player keys don't bypass connection limits
- ✅ Flexibility: Different streams can have different player keys accessing them
- ✅ Logging: Connection rejections clearly show both player key and resolved stream
Logging Output:
[INFO] player key validation SUCCESS, resolved to stream_id='publish/live/movie1'
[INFO] re-parsed validated stream: 'publish/live/movie1'
[WARN] refused, new player[192.168.1.100:12345], stream=publish/live/movie1, player limit reached (3/3)