TsgcWSAPIKeyManager provides full-lifecycle management for API keys issued by sgcWebSockets servers.
TsgcWSAPIKeyManager provides full-lifecycle management for API keys issued by sgcWebSockets servers. It generates cryptographically random keys with a configurable human-readable prefix, hashes them at rest with SHA-256, SHA-512 or Bcrypt, validates incoming requests by header or query string, rotates keys with a configurable grace period, revokes and renews keys, enforces scope-based authorization, maintains a persistent audit log and supports an optional IP allowlist. Keys are stored in memory or in an encrypted file that survives restarts.
The manager attaches to TsgcWebSocketHTTPServer and TsgcWSServer_HTTPAPI through the APIKeyManager property. Because the server’s automatic hooks only see the peer IP, actual request authentication is performed by calling IsRequestAuthorized from the server’s OnConnect event — this gives access to the full HTTP header block and the URL/query string so the configured X-API-Key header (or query parameter) can be parsed, validated and, if requested, matched against a required scope.
1. Drop a TsgcWSAPIKeyManager component on the form.
2. Configure Generation (prefix, length), Hashing (algorithm, salt) and Validation (header name, IP allowlist, FailClosed). See the per-section documentation below.
3. Assign the manager to a server component:
sgcWebSocketHTTPServer1.APIKeyManager := sgcWSAPIKeyManager1;
4. Wire the server’s OnConnect to call IsRequestAuthorized using the connection’s headers, URL and IP. Disconnect the client when it returns False.
5. Issue the first key for a customer and deliver the plaintext value to them exactly once — only the hashed form remains in storage afterwards.
What it does: Master switch for the whole component. When Enabled is False every public method short-circuits to "allowed" and the server auto-hooks become no-ops, so the key manager is effectively bypassed.
When to use it: Temporarily disable key enforcement during a controlled maintenance window or a staging deployment without unassigning the component from the server. Re-enable it when the change is done — no keys are lost, no scopes change, and the audit log keeps running the moment you flip it back on.
| Property | Type | Default | Description |
| Enabled | Boolean | True | Master switch. When False, ValidateKey / IsRequestAuthorized always return True and server auto-hooks become no-ops. |
sgcWSAPIKeyManager1.Enabled := True;
What it does: Controls how the plaintext key string is built by IssueKey. The key is a KeyPrefix followed by KeyLength random characters drawn from Charset. When IncludeChecksum is True a short checksum is appended so malformed keys can be rejected cheaply before a hash lookup.
When to use it: Branded keys for your SaaS — use sgc_live_aB3… in production and sgc_test_… on the staging cluster. The prefix makes it trivial for support staff to spot which environment a leaked key belongs to and for log scrubbers to redact it.
| Property | Type | Default | Description |
| Enabled | Boolean | True | When False, IssueKey raises instead of generating a new key. |
| KeyPrefix | string | ‘’ | Human-readable prefix prepended to the random body (for example sgc_live_). |
| KeyLength | Integer | 32 | Number of random characters in the body of the key. |
| Charset | string | A-Za-z0-9 | Alphabet used to generate the random body. Defaults to URL-safe alphanumerics when empty. |
| IncludeChecksum | Boolean | False | When True appends a short checksum suffix so obviously malformed keys can be rejected without a hash lookup. |
// Branded production keys: sgc_live_aB3...
sgcWSAPIKeyManager1.Generation.Enabled := True;
sgcWSAPIKeyManager1.Generation.KeyPrefix := 'sgc_live_';
sgcWSAPIKeyManager1.Generation.KeyLength := 32;
sgcWSAPIKeyManager1.Generation.IncludeChecksum := True;
What it does: Controls where hashed keys and the audit log live. kstMemory keeps everything in-process (cleared on shutdown). kstFile persists to FileName, optionally encrypted with EncryptionKey, and re-saves every AutoSaveSeconds seconds. kstCustom lets you implement your own SaveToFile / LoadFromFile hooks.
When to use it: Keep keys in memory during development for fast iteration. Switch to kstFile with EncryptAtRest := True for production so keys survive a server restart and are never exposed as plaintext on disk.
| Property | Type | Default | Description |
| StorageType | TsgcAPIKeyStorageType | kstMemory | kstMemory (volatile), kstFile (persist to FileName) or kstCustom (user-provided SaveToFile / LoadFromFile). |
| FileName | string | ‘’ | File path used by SaveToFile / LoadFromFile when StorageType is kstFile. |
| EncryptAtRest | Boolean | False | When True encrypts the key store on disk using EncryptionKey. |
| EncryptionKey | string | ‘’ | Secret used to encrypt the persisted key file. Must be supplied when EncryptAtRest is True. |
| AutoSaveSeconds | Integer | 0 | When > 0 the manager re-saves the key store to disk every AutoSaveSeconds seconds. |
// Production: encrypted file that survives restarts, auto-saved every 60s
sgcWSAPIKeyManager1.Storage.StorageType := kstFile;
sgcWSAPIKeyManager1.Storage.FileName := 'apikeys.dat';
sgcWSAPIKeyManager1.Storage.EncryptAtRest := True;
sgcWSAPIKeyManager1.Storage.EncryptionKey := 'my-very-secret-master-key';
sgcWSAPIKeyManager1.Storage.AutoSaveSeconds := 60;
sgcWSAPIKeyManager1.LoadFromFile('apikeys.dat');
What it does: Plaintext keys are never persisted. IssueKey hashes the generated key using Algorithm with an optional Salt and Iterations count; ValidateKey rehashes the incoming value and performs a constant-time comparison against the stored digest.
When to use it: Never store plaintext. SHA-256 is the sensible default for most deployments. Use SHA-512 for higher-security profiles (FIPS 140-2 Level 3). Use Bcrypt when you need deliberate key-stretching — slower validation but dramatically harder offline brute-force of a leaked key store.
| Property | Type | Default | Description |
| Algorithm | TsgcAPIKeyHashAlgorithm | khaSHA256 | Hash function used for at-rest storage: khaSHA256, khaSHA512 or khaBcrypt. |
| Salt | string | ‘’ | Static salt mixed into every hash. Change invalidates all existing keys. |
| Iterations | Integer | 1 | Number of hashing rounds. Increase for key-stretching at the cost of validation latency. |
// SHA-512 with a static salt and 10k iterations for key stretching
sgcWSAPIKeyManager1.Hashing.Algorithm := khaSHA512;
sgcWSAPIKeyManager1.Hashing.Salt := 'my-salt';
sgcWSAPIKeyManager1.Hashing.Iterations := 10000;
What it does: Catalog of allowed scope strings. When Enabled is True every scope attached to an issued key must exist in this list; unknown scopes are rejected. Individual catalog entries can be disabled without deleting them. ValidateKey / IsRequestAuthorized can require a specific scope per request.
When to use it: Fine-grained permissions. One customer’s key carries read:orders only and will be rejected on a write endpoint. A partner integration receives read:orders + write:shipments but never admin:*. Each request presents a scope hint and the manager enforces it consistently.
| Property | Type | Default | Description |
| Enabled | Boolean | True | Enables scope enforcement. When False, scope parameters are ignored. |
| Scopes | TCollection | empty | Collection of TsgcAPIKeyScopeItem entries defining the allowed scopes. |
Each TsgcAPIKeyScopeItem exposes:
| Property | Type | Default | Description |
| Name | string | ‘’ | Scope identifier (for example read:orders). Case-sensitive. |
| Description | string | ‘’ | Human-readable description shown in the object inspector and admin UIs. |
| Enabled | Boolean | True | Disables this scope without deleting it. Disabled scopes never satisfy HasScope. |
sgcWSAPIKeyManager1.Scopes.Enabled := True;
with sgcWSAPIKeyManager1.Scopes.Scopes.Add as TsgcAPIKeyScopeItem do
begin
Name := 'read:orders';
Description := 'List and read order history';
end;
with sgcWSAPIKeyManager1.Scopes.Scopes.Add as TsgcAPIKeyScopeItem do
begin
Name := 'write:shipments';
Description := 'Create and update shipment records';
end;
What it does: Makes key rotation transparent. RotateKey issues a fresh key for the same owner and scopes and marks the old one kksRotated. During the GracePeriodSec window both keys validate so the customer can swap without downtime. When AutoRotateDays is > 0 keys older than that threshold are rotated by the background sweep; NotifyBeforeExpirySec fires OnKeyExpired early so you can email the customer.
When to use it: A customer reports their key was leaked but their integration is still live. Rotate the key: they receive a new one, the old one keeps working for 24 hours so they can roll it through their infrastructure, then the old one is invalidated automatically.
| Property | Type | Default | Description |
| Enabled | Boolean | False | Enables rotation features (grace period and auto-rotate sweep). |
| GracePeriodSec | Integer | 86400 | Seconds the previous key stays valid after RotateKey. Default is 24 hours. |
| AutoRotateDays | Integer | 0 | When > 0 the background sweep rotates keys older than this many days. |
| NotifyBeforeExpirySec | Integer | 604800 | Fires OnKeyExpired this many seconds before a key actually expires. Default is 7 days. |
// 24h grace period, auto-rotate keys older than 90 days, notify 7 days ahead
sgcWSAPIKeyManager1.Rotation.Enabled := True;
sgcWSAPIKeyManager1.Rotation.GracePeriodSec := 86400;
sgcWSAPIKeyManager1.Rotation.AutoRotateDays := 90;
sgcWSAPIKeyManager1.Rotation.NotifyBeforeExpirySec := 7 * 86400;
What it does: Controls how issued keys age out. DefaultTTLSec is applied to IssueKey when the caller passes 0 as ExpiresInSec. EnforceExpiry toggles whether expired keys are rejected. CheckIntervalSec controls how often the background sweep scans for expired entries, fires OnKeyExpired and updates Stats.
When to use it: Partner integration keys must expire after 90 days — partners have to re-sign the agreement and fetch a new key. Setting DefaultTTLSec to 90×86400 means every IssueKey call respects that policy unless the caller explicitly overrides it.
| Property | Type | Default | Description |
| DefaultTTLSec | Integer | 0 | Default time-to-live for newly issued keys in seconds. 0 means never expires. |
| EnforceExpiry | Boolean | True | When True ValidateKey rejects keys whose ExpiresAt is in the past. |
| CheckIntervalSec | Integer | 60 | How often the background sweep scans for expired keys and fires OnKeyExpired. |
// Partner keys expire after 90 days, enforced strictly, swept every minute
sgcWSAPIKeyManager1.Expiration.DefaultTTLSec := 90 * 86400;
sgcWSAPIKeyManager1.Expiration.EnforceExpiry := True;
sgcWSAPIKeyManager1.Expiration.CheckIntervalSec := 60;
What it does: Stores per-key rate-limit defaults (max requests per window) alongside each key. These values are informational hints that a TsgcWSRateLimiter instance can read through the PerAPIKey scope to apply tier-appropriate throttling — the API key manager itself does not throttle traffic.
When to use it: Tiered SaaS. Free-tier customers are provisioned at 100 req/hour, Pro-tier at 10,000 req/hour, Enterprise at 1,000,000 req/hour. Configure DefaultMaxRequests / DefaultWindowSec on the API key manager and pair it with TsgcWSRateLimiter.PerAPIKey so the rate limiter reads the per-key budget automatically.
| Property | Type | Default | Description |
| Enabled | Boolean | False | Enables per-key rate-limit metadata alongside each issued key. |
| DefaultMaxRequests | Integer | 100 | Default maximum requests per window assigned to new keys. |
| DefaultWindowSec | Integer | 60 | Default window size in seconds associated with DefaultMaxRequests. |
// Default tier: 100 requests per hour per API key
sgcWSAPIKeyManager1.RateLimit.Enabled := True;
sgcWSAPIKeyManager1.RateLimit.DefaultMaxRequests := 100;
sgcWSAPIKeyManager1.RateLimit.DefaultWindowSec := 3600;
// Pair with TsgcWSRateLimiter.PerAPIKey for actual throttling
What it does: Records every key-lifecycle action (issued, validated, revoked, rotated, expired, scope granted/revoked, validation failed) to the in-memory ring buffer and, when LogFile is set, appends the same entries to a file. IncludeIP records the requester’s IP; IncludePayload captures additional details. RetentionDays prunes older entries; MaxMemoryEntries caps the ring buffer.
When to use it: Compliance requirement: every key operation must be logged and retained for 12 months. Enable audit with LogFile pointing at your SIEM drop zone and RetentionDays set to 365. OnAuditEvent fires for every entry so you can also ship them to a message bus.
| Property | Type | Default | Description |
| Enabled | Boolean | True | Enables the audit log. When False no entries are recorded and OnAuditEvent never fires. |
| LogFile | string | ‘’ | Optional file path the audit log is appended to. |
| IncludeIP | Boolean | True | When True every entry records the requester’s IP. |
| IncludePayload | Boolean | False | When True the Details field of each audit entry carries additional context. |
| RetentionDays | Integer | 90 | Entries older than this many days are pruned by the background sweep. |
| MaxMemoryEntries | Integer | 10000 | Maximum number of entries kept in the in-memory ring buffer. |
// Compliance: 12-month retention, log to file, include IP + payload
sgcWSAPIKeyManager1.Audit.Enabled := True;
sgcWSAPIKeyManager1.Audit.LogFile := 'apikeys-audit.log';
sgcWSAPIKeyManager1.Audit.IncludeIP := True;
sgcWSAPIKeyManager1.Audit.IncludePayload := True;
sgcWSAPIKeyManager1.Audit.RetentionDays := 365;
sgcWSAPIKeyManager1.Audit.MaxMemoryEntries := 50000;
What it does: Tells the manager how clients present their keys and under what conditions requests are accepted. HeaderName (default X-API-Key) and QueryParamName (default api_key) are the two places ExtractKeyFromHeaders / ExtractKeyFromQuery look. RequireHTTPS rejects plaintext traffic. IPAllowlist restricts keys to specific source IPs. FailClosed (default True) rejects any request the manager cannot positively authorize.
When to use it: Corporate-only API: require HTTPS for every request and restrict keys to the office egress CIDR range so even a leaked key cannot be abused from outside the corporate network.
| Property | Type | Default | Description |
| HeaderName | string | X-API-Key | HTTP header name used to read the incoming key. |
| QueryParamName | string | api_key | Query-string parameter used as fallback when the header is absent. |
| RequireHTTPS | Boolean | False | When True rejects every non-HTTPS request regardless of key validity. |
| IPAllowlist | TStringList | empty | When non-empty, only requests whose IP appears in the list are authorized. |
| FailClosed | Boolean | True | When True any request the manager cannot positively authorize is rejected. |
// Corporate API: HTTPS-only, allow only office egress IPs, fail closed
sgcWSAPIKeyManager1.Validation.HeaderName := 'X-API-Key';
sgcWSAPIKeyManager1.Validation.QueryParamName := 'api_key';
sgcWSAPIKeyManager1.Validation.RequireHTTPS := True;
sgcWSAPIKeyManager1.Validation.FailClosed := True;
sgcWSAPIKeyManager1.Validation.IPAllowlist.Add('203.0.113.10');
sgcWSAPIKeyManager1.Validation.IPAllowlist.Add('203.0.113.11');
What it does: Read-only counter object that exposes live statistics about the key store. All counters are updated atomically inside the component’s internal critical section.
When to use it: Live admin dashboard. Poll Stats from a TTimer and paint the current ActiveKeys / RevokedKeys / TotalValidations / TotalValidationFailures so operators can see authentication pressure in real time.
| Property | Type | Description |
| TotalKeys | Int64 | Total number of keys ever issued by this manager instance. |
| ActiveKeys | Int64 | Number of keys currently in status kksActive. |
| RevokedKeys | Int64 | Number of keys currently in status kksRevoked. |
| ExpiredKeys | Int64 | Number of keys currently in status kksExpired. |
| TotalValidations | Int64 | Total number of ValidateKey / IsRequestAuthorized calls. |
| TotalValidationFailures | Int64 | Total number of validation calls that rejected the key. |
| LastIssuedAt | TDateTime | Timestamp of the most recent IssueKey call. |
// Poll from a TTimer and update a dashboard
procedure TForm1.StatsTimer(Sender: TObject);
begin
LabelActive.Caption := Format('Active: %d', [sgcWSAPIKeyManager1.Stats.ActiveKeys]);
LabelRevoked.Caption := Format('Revoked: %d', [sgcWSAPIKeyManager1.Stats.RevokedKeys]);
LabelValidations.Caption := Format('Validations: %d', [sgcWSAPIKeyManager1.Stats.TotalValidations]);
LabelFailures.Caption := Format('Failures: %d', [sgcWSAPIKeyManager1.Stats.TotalValidationFailures]);
end;
OnKeyIssued: Fired when a new key has been issued. Provides the owner, the raw plaintext key (last chance to observe it) and the scope array. Deliver the plaintext to the customer from this handler.
OnKeyValidated: Fired every time ValidateKey completes. Provides the key, a boolean aValid flag and a machine-readable reason string.
OnKeyRevoked: Fired when a key has been revoked. Provides the key and the revocation reason.
OnKeyRotated: Fired when a key has been rotated. Provides the old key and the new key so you can notify the customer.
OnKeyExpired: Fired by the background expiry sweep when a key’s ExpiresAt has elapsed (or NotifyBeforeExpirySec seconds earlier).
OnValidation: Final decision hook fired during ValidateKey. Set Allow := False and provide a Reason to reject an otherwise-valid key (custom business rules).
OnAuditEvent: Fired for every audit log entry. Provides the TsgcAPIKeyAuditEntry record (Timestamp, KeyPrefix, Action, IP, Details) — ideal for forwarding to an external SIEM.
| Method | Description |
| IssueKey(Owner, Scopes, ExpiresInSec) | Generates, hashes and stores a new key; returns the plaintext (only time it can be seen). Pass ExpiresInSec = 0 to inherit Expiration.DefaultTTLSec. |
| ValidateKey(Key, RequiredScope, IP) | Validates a raw key. Optionally enforces a required scope and records the requester’s IP. Returns True on success. |
| RevokeKey(Key, Reason) | Marks a key as revoked; subsequent ValidateKey calls return False. |
| RotateKey(OldKey, out NewKey) | Issues a new key for the same owner and scopes; the old key is marked kksRotated and honored during GracePeriodSec. |
| RenewKey(Key, NewExpiresInSec) | Extends the key’s expiration by the given number of seconds from now. |
| GetKeyInfo(Key) | Returns a TsgcAPIKeyInfo record with owner, scopes, status, timestamps and usage count. |
| GetKeysByOwner(Owner) | Returns hashed-key identifiers associated with the given owner. |
| ListScopes(Key) | Returns the scopes attached to a key. |
| IsExpired(Key) | Returns True if the key is past its expiry. |
| IsRevoked(Key) | Returns True if the key is revoked. |
| KeyExists(Key) | Returns True if the key is known (active, revoked or expired). |
| GrantScope(Key, Scope) | Adds a scope to an existing key. |
| RevokeScope(Key, Scope) | Removes a scope from an existing key. |
| HasScope(Key, Scope) | Returns True if the key carries the given scope. |
| GetAuditLog(Key, MaxEntries) | Returns audit entries; when Key is empty, returns entries for all keys. |
| ClearAuditLog(Key) | Clears the audit log for a specific key or (when empty) for all keys. |
| SaveToFile(FileName) | Persists keys and audit log to the given file (falls back to Storage.FileName when empty). |
| LoadFromFile(FileName) | Restores keys and audit log from file. |
| ExportKeys(Stream) | Stream-based export of keys and audit log. |
| ImportKeys(Stream) | Stream-based import of keys and audit log. |
| PurgeExpired | Removes all keys whose ExpiresAt is in the past. |
| Count | Returns the number of keys currently stored. |
| IsRequestAuthorized(Headers, QueryString, IP, RequiredScope) | One-shot authorization check: extracts the key from headers or query string, validates it and optionally enforces a scope. |
| ExtractKeyFromHeaders(Headers) | Parses the Validation.HeaderName value out of a raw HTTP header block. |
| ExtractKeyFromQuery(QueryString) | Parses the Validation.QueryParamName value out of a raw query string. |
| IsConnectionAllowed(IP) | Server hook: returns False when FailClosed is True and IPAllowlist is non-empty and IP is not listed. Called automatically. |
| IsMessageAllowed(IP, Message) | Server hook called automatically for every inbound message. IP-level no-op by default. |
| RegisterConnection(IP) | Tracks a new connection. Called automatically by the server. |
| UnregisterConnection(IP) | Releases tracking for a connection. Called automatically on disconnect. |
When assigned to a server’s APIKeyManager property, the component automatically:
// Enable and configure the API key manager
sgcWSAPIKeyManager1.Enabled := True;
// Branded keys
sgcWSAPIKeyManager1.Generation.KeyPrefix := 'sgc_live_';
sgcWSAPIKeyManager1.Generation.KeyLength := 32;
// Persist to an encrypted file that survives restarts
sgcWSAPIKeyManager1.Storage.StorageType := kstFile;
sgcWSAPIKeyManager1.Storage.FileName := 'apikeys.dat';
sgcWSAPIKeyManager1.Storage.EncryptAtRest := True;
sgcWSAPIKeyManager1.Storage.EncryptionKey := 'master-secret';
sgcWSAPIKeyManager1.Storage.AutoSaveSeconds := 60;
// SHA-256 with a static salt
sgcWSAPIKeyManager1.Hashing.Algorithm := khaSHA256;
sgcWSAPIKeyManager1.Hashing.Salt := 'my-salt';
// Scope catalog
sgcWSAPIKeyManager1.Scopes.Enabled := True;
with sgcWSAPIKeyManager1.Scopes.Scopes.Add as TsgcAPIKeyScopeItem do
begin Name := 'read:orders'; Description := 'Read orders'; end;
with sgcWSAPIKeyManager1.Scopes.Scopes.Add as TsgcAPIKeyScopeItem do
begin Name := 'write:orders'; Description := 'Create orders'; end;
// 90-day default TTL and 24-hour rotation grace period
sgcWSAPIKeyManager1.Expiration.DefaultTTLSec := 90 * 86400;
sgcWSAPIKeyManager1.Rotation.Enabled := True;
sgcWSAPIKeyManager1.Rotation.GracePeriodSec := 86400;
// Full audit log, 12-month retention
sgcWSAPIKeyManager1.Audit.Enabled := True;
sgcWSAPIKeyManager1.Audit.LogFile := 'apikeys-audit.log';
sgcWSAPIKeyManager1.Audit.IncludeIP := True;
sgcWSAPIKeyManager1.Audit.RetentionDays := 365;
// Validation: header-based, fail closed
sgcWSAPIKeyManager1.Validation.HeaderName := 'X-API-Key';
sgcWSAPIKeyManager1.Validation.QueryParamName := 'api_key';
sgcWSAPIKeyManager1.Validation.FailClosed := True;
// Load previously saved keys and attach to server
sgcWSAPIKeyManager1.LoadFromFile('apikeys.dat');
sgcWebSocketHTTPServer1.APIKeyManager := sgcWSAPIKeyManager1;
sgcWebSocketHTTPServer1.Active := True;
// Issue a new key for a customer (plaintext returned only here)
var
LKey, LNewKey: string;
begin
LKey := sgcWSAPIKeyManager1.IssueKey('customer-123',
TArray<string>.Create('read:orders', 'write:orders'), 30 * 86400);
ShowMessage('Deliver to customer: ' + LKey);
end;
// Authorize every incoming connection in OnConnect
procedure TForm1.ServerConnect(Connection: TsgcWSConnection);
begin
if not sgcWSAPIKeyManager1.IsRequestAuthorized(
Connection.HeadersRequest.Text, Connection.URL, Connection.IP, 'read:orders') then
Connection.Disconnect;
end;
// Rotate a leaked key (old one valid 24h during grace)
sgcWSAPIKeyManager1.RotateKey(LKey, LNewKey);
// Revoke a key on subscription cancellation
sgcWSAPIKeyManager1.RevokeKey(LNewKey, 'customer cancelled subscription');
The API key manager is fully thread-safe. All public methods use internal critical sections and thread-safe lists to protect concurrent access to the key store, scope index, owner index and audit log. A single component can be shared by multiple server instances and accessed from any thread (server event handlers, timer threads, background workers) without external synchronization.