Skip to content

Commit 6d4ab83

Browse files
kevin1chunclaude
andcommitted
docs: add full auth flow and token storage diagrams to README
Move example section higher for better visibility and expand authentication section with ASCII flow diagrams, browser-based auth explanation, and encrypted token storage details. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 98f05ae commit 6d4ab83

File tree

1 file changed

+110
-6
lines changed

1 file changed

+110
-6
lines changed

README.md

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,16 @@ Add to your MCP client's config (e.g. `~/Library/Application Support/Claude/clau
101101
```
102102
</details>
103103

104-
## Authenticate
105-
106-
Start your agent and say "setup robinhood" (or call `robinhood_browser_login` directly). Chrome will open to the real Robinhood login page — log in with your credentials and MFA. The session is cached and auto-restores for ~24 hours.
107-
108104
## Example
109105

110106
> "Buy 1 50-delta SPX call expiring tomorrow"
111107
112108
![SPX options chain with greeks and order summary](docs/images/spx-options-example.png)
113109

110+
## Authenticate
111+
112+
Start your agent and say "setup robinhood" (or call `robinhood_browser_login` directly). Chrome will open to the real Robinhood login page — log in with your credentials and MFA. The session is cached and auto-restores for ~24 hours.
113+
114114
## MCP Tools (18)
115115

116116
All 18 tools work with every MCP-compatible agent.
@@ -181,12 +181,116 @@ const portfolio = await client.buildHoldings();
181181

182182
## Authentication
183183

184-
Sessions are cached to `~/.rh-for-agents/session.enc` (AES-256-GCM encrypted, key in OS keychain). Authentication uses browser-based login — `robinhood_browser_login` opens Chrome to the real Robinhood login page where you handle MFA natively. After initial login, subsequent authentication is automatic until the token expires (~24 hours).
185-
186184
**MCP**: Call `robinhood_browser_login` to open Chrome and log in (works with all agents). After that, all tools auto-restore the cached session.
187185

188186
**Skills**: Run the `robinhood-setup` skill for guided browser login (Claude Code and OpenClaw).
189187

188+
### Full Auth Flow
189+
190+
```
191+
┌─────────────────────────────────────────────────────────────────────────┐
192+
│ │
193+
│ robinhood_browser_login restoreSession() │
194+
│ (first-time / expired) (every tool call) │
195+
│ │ │ │
196+
│ ▼ ▼ │
197+
│ ┌───────────────────┐ loadTokens() │
198+
│ │ Playwright launches│ Bun.secrets.get() from │
199+
│ │ system Chrome │ OS keychain │
200+
│ │ (headless: false) │ (macOS Keychain Services) │
201+
│ └────────┬──────────┘ │ │
202+
│ │ │ │
203+
│ ▼ ▼ │
204+
│ ┌───────────────────┐ Set Authorization header │
205+
│ │ Navigate to │ Validate: GET /positions/ │
206+
│ │ robinhood.com/login│ │ │
207+
│ └────────┬──────────┘ ┌─────┴─────┐ │
208+
│ │ Valid? Invalid? │
209+
│ ▼ │ │ │
210+
│ ┌───────────────────┐ return ┌──┘ │
211+
│ │ User logs in │ "cached" │ │
212+
│ │ (email, password, │ ▼ │
213+
│ │ MFA push/SMS) │ POST /oauth2/token/ │
214+
│ └────────┬──────────┘ (grant_type: refresh_token, │
215+
│ │ expires_in: 734000) │
216+
│ ▼ │ │
217+
│ ┌───────────────────────────┐ ┌──────┴──────┐ │
218+
│ │ Robinhood frontend calls │ Success? Failure? │
219+
│ │ POST /oauth2/token │ │ │ │
220+
│ │ │ saveTokens() throw │
221+
│ │ Playwright intercepts: │ return AuthError │
222+
│ │ request → device_token │ "refreshed" "Use browser_login" │
223+
│ │ response → access_token, │ │
224+
│ │ refresh_token │ │
225+
│ └────────┬──────────────────┘ │
226+
│ │ │
227+
│ ▼ │
228+
│ saveTokens() ──► token-store.ts │
229+
│ │ Bun.secrets.set() → OS keychain │
230+
│ │ (tokens never written to disk) │
231+
│ │ │
232+
│ │ │
233+
│ ▼ │
234+
│ restoreSession() ──► client ready │
235+
│ getAccountProfile() → account_hint │
236+
│ Close browser │
237+
│ │
238+
└─────────────────────────────────────────────────────────────────────────┘
239+
```
240+
241+
The left path is the initial login (browser-based, user-interactive). The right path is the session restore (automatic, every tool call). When the cached access token is invalid, it attempts a silent refresh using the stored `refresh_token` (with `expires_in: 734000` ~8.5 days). If refresh also fails, the user is directed back to browser login.
242+
243+
### Why Browser-Based Auth
244+
245+
The browser login is purely passive — Playwright never clicks buttons, fills forms, or predicts the login flow. It opens a real Chrome window, the user completes login entirely on their own (including whatever MFA Robinhood requires), and Playwright only intercepts the network traffic:
246+
247+
- `page.on("request")` captures `device_token` from POST body to `/oauth2/token`
248+
- `page.on("response")` captures `access_token` + `refresh_token` from the 200 response
249+
250+
This design is resilient to Robinhood UI changes — it doesn't depend on any DOM selectors, page structure, or login step ordering. As long as the OAuth token endpoint exists, the interception works. `playwright-core` is used (not `playwright`) so no browser binary is bundled — it drives the user's system Chrome.
251+
252+
### Encrypted Token Storage
253+
254+
```
255+
┌─ token-store.ts ──────────────────────────────────────────────────┐
256+
│ │
257+
│ SAVE │
258+
│ ──── │
259+
│ TokenData (JSON): │
260+
│ {access_token, refresh_token, token_type, device_token, saved_at} │
261+
│ │ │
262+
│ ▼ │
263+
│ JSON.stringify() │
264+
│ │ │
265+
│ ▼ │
266+
│ Bun.secrets.set("rh-for-agents", "session-tokens", json) │
267+
│ → OS encrypts and stores in keychain │
268+
│ → No file written to disk │
269+
│ │
270+
│ │
271+
│ LOAD │
272+
│ ──── │
273+
│ Bun.secrets.get("rh-for-agents", "session-tokens") │
274+
│ │ │
275+
│ ▼ │
276+
│ JSON.parse() → TokenData │
277+
│ │
278+
│ │
279+
│ STORAGE │
280+
│ ─────── │
281+
│ Primary: OS Keychain via Bun.secrets │
282+
│ ├── macOS: Keychain Services │
283+
│ ├── Linux: libsecret (GNOME Keyring, KWallet) │
284+
│ └── Windows: Credential Manager │
285+
│ Tokens never touch the filesystem. │
286+
│ │
287+
│ Fallback: plaintext JSON (~/.rh-for-agents/session.json) │
288+
│ (CI environments, minimal installs without keychain) │
289+
└────────────────────────────────────────────────────────────────────┘
290+
```
291+
292+
`Bun.secrets` stores tokens directly in the OS keychain — no intermediate encryption layer needed since the keychain itself provides encryption, access control, and tamper resistance. When `Bun.secrets` is unavailable (CI, headless servers), tokens fall back to a plaintext JSON file with a console warning.
293+
190294
## Development
191295

192296
```bash

0 commit comments

Comments
 (0)