Broker OpenAPI Token Auto-Renewal: A Design That Fails Loudly
TECH INSIGHT / EXPLAINED
Token lifetime is an expiry-time problem, not a calendar-date problem. A broker client should know which credential a request needs, when it expires, and how to coordinate renewal without replaying uncertain orders.
REST and WebSocket credentials are different
KIS uses an access token for REST authorization and an approval key for WebSocket access. Keep their lifecycle and environment selection separate. For REST, record the expiry information returned by issuance rather than assuming midnight expiration or hard-coding 24 hours. Parse the provider’s timezone convention explicitly and use a bounded safety margin for clock skew and request duration.
from datetime import datetime, timedelta, timezone
def needs_renewal(expires_at, now, margin=timedelta(minutes=2)):
if expires_at.tzinfo is None or now.tzinfo is None:
raise ValueError("timezone-aware times required")
if margin.total_seconds() < 0:
raise ValueError("negative margin")
return now + margin >= expires_at
expiry = datetime(2026, 9, 30, 14, 0, tzinfo=timezone.utc)
assert not needs_renewal(expiry, datetime(2026, 9, 30, 0, 1, tzinfo=timezone.utc))
assert needs_renewal(expiry, datetime(2026, 9, 30, 13, 59, tzinfo=timezone.utc))Use one renewal in flight
Concurrent callers should share a renewal operation or wait on its result. After acquiring the renewal lock, check freshness again because another caller may already have renewed it. Respect the issuer’s documented rate limits, use timeouts/backoff and atomically publish the new credential plus expiry. Readers must not observe a token from one generation and expiry from another.
Do not renew on every failure
A timeout, authorization denial, insufficient permission, or rate-limit response is not automatically token expiration. Interpret documented HTTP and broker error codes. For an explicitly rejected read request, a bounded renew-and-retry can be appropriate. For a submitted order with an unknown outcome, reconcile its ID and fills before deciding on any new submission. Renewal does not make an arbitrary order retry safe.
Degrade visibly without stopping recovery
If a required credential is unusable, block new risk-increasing actions and alert with a redacted reason. Keep status reporting and recovery tasks active where they can run safely. A pre-session check can reduce avoidable latency but should not force needless issuance when a token remains valid. Store credentials in a protected store and never print token responses, authorization headers or app secrets.
Do not let a late error invalidate a newer token
Read A can leave with token generation 7, remain in flight, and report an authentication failure after another caller has published generation 8. Unconditionally invalidating the global token would discard the newer credential. Capture the generation used to construct each request, and invalidate only when that generation still matches the current state. Perform the comparison and mutation under the same lock, or atomic comparison boundary, used to publish replacements.
This offline sample illustrates generation comparison only. It contains no credentials, issuance calls, or synchronization implementation. A read retry still needs its own attempt limit; successfully acquiring a newer generation must not grant unlimited retries.
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class TokenState:
generation: int
valid: bool
def invalidate_if_current(state, failed_generation):
if state.generation != failed_generation:
return state
return replace(state, valid=False)
current = TokenState(generation=8, valid=True)
assert invalidate_if_current(current, 7) == current
assert not invalidate_if_current(current, 8).valid
Separate renewal activity from credential usability
A token being replaced proactively may still be usable. Instead of representing everything with a single “refreshing” state, track current credential validity, renewal activity, and the next permitted attempt separately. Whether the old token remains usable after renewal fails depends on its remaining lifetime and the provider's error semantics. Never continue using it after actual expiry or explicit invalidation.
The official KIS authentication example demonstrates saving the issued token and returned expiry timestamp. Generation tracking and failure policy are additional application design choices. Do not treat the sample's file-storage arrangement as a complete deployment design. Separate live and simulation environments, and identify stored credentials with a non-secret configuration alias.
Two processes have two in-memory locks
If several workers use the same credentials, a lock inside each process cannot prevent concurrent issuance across them. At modest scale, appointing one authentication owner is often simpler. A shared-store design needs renewal ownership, recovery from an abandoned lock, and conditional versioned writes. A very long lease can delay recovery when its owner dies.
Compare the version observed when renewal began with the version at publication so a delayed former owner cannot overwrite a newer result. This protects local state; it cannot reverse duplicate issuance already performed by the provider. After a restart, validate the stored environment, expiry, and record format before reusing a token. Treat failed validation as an explicit recovery condition rather than an invitation to guess missing fields.
Test time boundaries with an injected clock
A useful educational test set includes concurrent reads near expiry, delayed renewal responses, stale-generation authentication errors, renewal failure followed by actual expiry, and process termination just before persistence. Inject fixed responses instead of calling the broker. Inspect issuance counts and published generations. Canceling one waiting request should not automatically cancel a shared renewal still needed by other waiters.
Store expiry as an absolute timezone-aware timestamp; use a monotonic clock for local waits and timeout intervals to resist wall-clock adjustments. Do not restore a persisted monotonic reading across process restarts. For the unknown-order case, make the acceptance criterion explicit: restoring authentication must leave the order-submission count unchanged and route the workflow into reconciliation. These tests evaluate client coordination, not broker availability or live trading performance.
Documentation and next steps
Content and examples reviewed: 2026-10-01
댓글
댓글 쓰기