Open API token lifecycle
cTrader's Open API allows one valid access token per cTrader ID (cID) ที่ time moment new token issued — scheduled refresh หรือ re-authorization เมื่อ user links another account on same cID — previous access token invalidated copy engine running on remote node holding now-dead token ดังนั้น new token must reach มัน ไม่มี dropping live connection
Model
OpenApiAuthorizationaggregate ที่ holds cID ของ encrypted access + refresh tokens unique index on(UserId, CtidUserId)enforces exactly one authorization per cID per userTokenVersion— monotonic counter bumped ทุก time token rotates (Refresh()which also covers re-auth path เมื่อ another account linked on same cID) มัน version marker สำหรับ single-valid-token rule และ คือ what running host uses ไป detect change even ถ้า two token strings happen ไป collide- tokens encrypted at rest ผ่าน
ISecretProtector(EncryptionPurposes.OpenApiAccessToken/OpenApiRefreshToken) พวกเขา never logged หรือ stored ใน plaintext
Propagation (graceful in-place swap)
- token rotates → new token + bumped
TokenVersionpersisted CopyEngineSupervisoron hosting node re-reads plan ทุก reconcile cycle และ computes token signature (access tokens + versions) change means rotation- แทน tearing down host และ restarting (which would drop master ของ execution stream) supervisor pushes new token ไป running host
- host re-authenticates affected account on existing socket
(
ProtoOAAccountAuthReqagain) ผ่านSwapAccessTokenAsyncจากนั้น does light reconcile old token dies; copy stream never stops
นี้ คือ what makes cross-cID case safe: user adding second account จาก same cID mid-run invalidates old token และ running copy profile keeps going on new one
Refresh
OpenApiTokenRefreshService (background) proactively refreshes authorizations ก่อน expiry;
OpenApiAuthorization.IsExpiring(threshold, now) gates มัน cTrader rotates refresh token
on ทุก refresh ดังนั้น new refresh token persisted immediately; read-only cache ที่ can't
persist would self-invalidate (relevant ไป in-cluster test Job ซึ่ง mounts writable copy
ของ secret)
Failure escalation
failed refresh ไม่ silent OpenApiAuthorization.MarkRefreshFailed(reason, now, criticalWindow)
records RefreshFailedAt increments ConsecutiveRefreshFailures และ always raises
AccessTokenRefreshFailed (warning) เมื่อ token now ใน App:OpenApi:TokenRefreshCriticalWindow
(default 6h) ของ expiry และ refresh still failing มัน escalates once ด้วย
AccessTokenRefreshCritical domain event + Critical log ดังนั้น owner สามารถ re-authorize ก่อน
copy/prop-firm operations lose token failure counter และ escalation latch reset on next
successful Refresh service keeps retrying ทุก TokenRefreshInterval ดังนั้น provider/maintenance
outage self-heals เมื่อ refresh endpoint returns
Invalidation alert & auto-recovery (M1)
partial/again-authorization on cID invalidates token running copy host still holds เมื่อ
trading call rejects ด้วย OpenApiErrorKind.TokenInvalid host raises distinct
CopyTokenInvalidated alert (log 1078) — ไม่ generic failure — ดังนั้น notification channel
knows token needs attention recovery automatic: supervisor re-reads authorization ทุก cycle และ
เมื่อ refreshed token changes token signature มัน pushes มัน ไป running host สำหรับ in-place
swap — copying resumes ด้วย ไม่มี manual re-add NotLinkable profile (token/auth temporarily
unresolvable) likewise re-evaluated ทุก supervisor cycle และ hosted moment plan ของมัน builds again
Host liveness watchdog (M2)
supervisor watches ทุก hosted profile ของ run task ถ้า host exits หรือ faults ขณะที่ profile ของมัน
still assigned ไป node นี้ watchdog cancels และ restarts มัน next cycle (log
CopyHostRestarted) ดังนั้น wedged host self-heals แทน needing manual restart — และ one profile ของ
failure never stalls others (per-profile isolation)
Tests
- Unit —
TokenVersionbumps onRefresh; host performs in-place swap ไม่มี restart; cross-cID invalidation swaps source และ destination tokens; an invalidated destination token raisesCopyTokenInvalidatedและ auto-recovers on next token push (M1); watchdogIsHostDeaddecision restarts completed/faulted host และ leaves reassigned profile alone (M2) - Integration —
TokenVersionpersists + increments ผ่าน EF on real Postgres; token signature changes on version bump even ถ้า string unchanged