Broker Platform
Mirrored from
docs/*.md in the Aegis repositoryBroker Platform
» The single broker integration layer for the entire ecosystem. One OAuth
» callback endpoint for every broker. Applications never implement broker
» OAuth directly. The callback reads the signed OAuth state to determine
» WHICH application initiated the flow, then redirects the user back to that
» app with its workflow restored. Token exchange, refresh, broker sessions,
» WebSocket lifecycle and the capability registry belong exclusively to this
» platform.
**Status: 🟡 Implemented (Aegis migrated; FlipTrade registered, migration documented — prod untouched)**
0. THE SEVEN PLATFORM-OWNED CAPABILITIES
# | Capability | Owner in this module
1 | OAuth initiation | `runtime.ts` → `startFlow()`
2 | Unified callback (one endpoint, all brokers/apps) | `runtime.ts` → `handleCallback()`
3 | Token exchange | providers (`zerodha.ts` / `groww.ts`) → `exchange()`
4 | Refresh tokens | providers → `refresh()`; `restoreSession()` (Kite: no refresh token — re-auth, reported honestly)
5 | Broker sessions | `sessions.ts` (per app+broker, IndexedDB + fallback)
6 | WebSocket lifecycle | `websocket.ts` → `WebSocketLifecycle` (connect, subscribe, reconnect backoff, staleness)
7 | Broker capability registry | `capabilities.ts` → `brokerCapabilityRegistry`
Applications only call `startFlow()`, `restoreSession()`, and the capability
registry. Everything else is platform-owned.
---
1. WHY THIS EXISTS
Previously each application implemented broker OAuth itself:
• Aegis: built the Kite login URL + handled `/auth/callback` + exchanged the
token + stored the session (in `src/plugins/broker-zerodha.ts`,
`src/app/auth/callback/page.tsx`, `src/platform/broker/*`).
• FlipTrade: did the same in FastAPI (`api.thytrade.xyz/api/v1/auth/zerodha/callback`).
Every future product would have copied this again. That is an architectural
duplication: broker OAuth is a **platform capability**, not an application
capability. This module centralizes it.
2. ARCHITECTURE
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ Aegis │ │ FlipTrade │ │ Future apps │ ← applications
└──────┬──────┘ └──────┬──────┘ └──────┬───────┘
│ startFlow() │ startFlow() │ startFlow()
└─────────────────┴─────────────────┘
▼
┌───────────────────────────────────┐
│ BROKER AUTH PLATFORM │ src/platform/broker-auth/
│ · app registry (who may auth) │
│ · broker providers (zerodha/groww)│
│ · signed expiring state │ state.ts
│ · startFlow / handleCallback │ runtime.ts
│ · session store (per-app) │ sessions.ts
│ · reconnect / expiry logic │
└──────────────┬────────────────────┘
│ login URL + signed state
▼
┌─────────────────────────────┐
│ Broker (Kite / Groww / …) │
└──────────────┬──────────────┘
│ redirect → THE ONE callback
│ (Kite: redirect_params echo;
│ OAuth: state param)
▼
┌───────────────────────────────────┐
│ /auth/broker/callback │ ← one endpoint, all brokers
│ 1. verify state (sig/expiry) │
│ 2. WHICH app? state.app │
│ 3. exchange token (provider) │
│ 4. persist session (platform) │
│ 5. redirect → app.homeUrl+path │
└───────────────────────────────────┘
Key decisions
Decision | Why
State is **signed + expiring** (HMAC-SHA256, 10 min TTL) | Routing to the originating app must not be forgeable (open-redirect protection); stale callbacks must fail
State carries `{app, broker, redirectTo, nonce, exp, sig}` | The callback knows exactly which app + workflow to restore
Kite uses **`redirect_params`**, standard OAuth uses **`state`** | Kite Connect has no `state` param; `redirect_params` is its documented echo mechanism. The provider contract abstracts the transport (`stateTransport`)
Sessions keyed **per app + broker** in one store | Multiple apps share the platform without colliding
Credentials staged **per app + broker** in localStorage, 30 min TTL | Callback is a separate page load; the exchange needs the secret, but it never travels in the state
`createPlatformBrokerStorage(appId)` adapts the store to the SDK `BrokerStorage` | The broker runtime (execution layer) reads sessions through the platform — one source of truth
FlipTrade **registered but prod untouched** | Requirement: existing FlipTrade functionality continues unchanged; migration documented below
3. MODULE LAYOUT
src/platform/broker-auth/
types.ts BrokerApp, BrokerAuthProvider, state payload, results
state.ts createState / verifyState (HMAC-SHA256, expiry, nonce)
registry.ts registerApp / registerProvider + built-ins (aegis, fliptrade; zerodha, groww)
sessions.ts per-app session store (IndexedDB + memory fallback) + BrokerStorage adapter
capabilities.ts PLATFORM-OWNED capability registry (brokerCapabilityRegistry)
websocket.ts PLATFORM-OWNED WebSocket lifecycle (WebSocketLifecycle)
providers/
zerodha.ts Kite: login URL w/ redirect_params, checksum exchange, 06:00 IST expiry
groww.ts Standard OAuth: `state` param, code exchange, refresh_token
runtime.ts startFlow / handleCallback / manualExchange / restoreSession
index.ts public surface: `import { … } from "@/platform/broker-auth"`
4. PUBLIC API (WHAT AN APP CALLS)
import { startFlow, handleCallback, manualExchange, restoreSession } from "@/platform/broker-auth";
// 1. Initiate — app names itself + its workflow-restore path.
const { loginUrl } = await startFlow({
app: "aegis",
broker: "zerodha", // platform broker id (not the runtime adapter id)
redirectTo: "/app?broker=zerodha",
credentials: { apiKey, apiSecret },
});
// 2. User authorizes at the broker. Broker redirects to the ONE callback.
// (Aegis hosts it at /auth/broker/callback — same page for every broker.)
// 3. Callback handler — inside the endpoint only.
const result = await handleCallback({ broker, requestToken, state });
if (result.ok) window.location.replace(result.redirectUrl); // back to app + workflow
// 4. App-side restore — never touch tokens.
const r = await restoreSession("aegis", "zerodha");
// r.status: "connected" | "expired" | "none"
// 5. Paste-token fallback (no redirect) — exchange still platform-owned.
const r2 = await manualExchange({ app: "aegis", broker: "zerodha", credentials, requestToken });
Apps do NOT: build broker URLs, read `request_token`, compute checksums,
store access tokens, handle expiry, or reconnect. The platform does all of it.
5. KITE-SPECIFIC NOTES (VERIFIED AGAINST OFFICIAL DOCS)
• Login endpoint: `https://kite.zerodha.com/connect/login?v=3&api_key=<key
… (truncated — see repository docs/*.md for the full document)This is a condensed in-app view. The full document lives in the repository at
docs/broker_platform.md.