openclaw_oauth_sync/docs/ARCHITECTURE.md
shamid202 22731fff60 Add complete OAuth token refresh and sync solution
- Setup wizard with auto-detection of OpenClaw paths and Claude CLI
- Token sync watcher (inotifywait) for real-time credential updates
- Auto-refresh trigger timer that runs Claude CLI every 30 min
- Supports Claude CLI in Docker container or on host
- Temporary ANTHROPIC_BASE_URL override for container environments
- Anthropic model configuration for OpenClaw
- Auth profile management (fixes key vs access field)
- Systemd services and timers for both sync and trigger
- Comprehensive documentation and troubleshooting guides
- Re-authentication notification system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 01:51:18 +07:00

119 lines
6.4 KiB
Markdown

# Architecture
## Token Flow Diagram
```
+--------------------+ auto-refresh +------------------------------------------+
| Claude Code CLI | ================> | .credentials.json |
| (inside | (every ~8 hours, | { |
| claude-proxy | built-in to CLI) | "claudeAiOauth": { |
| container) | | "accessToken": "sk-ant-oat01-...", |
+--------------------+ | "refreshToken": "sk-ant-ort01-...", |
| "expiresAt": 1772120060006 |
| } |
| } |
+-------------------+-----------------------+
|
inotifywait detects
CLOSE_WRITE / MOVED_TO
|
+-------------------v-----------------------+
| sync-oauth-token.sh |
| (systemd service, runs continuously) |
+---+----------------+----------------+-----+
| | |
+----------------+ +-----------+---------+ |
| | | |
+---------v---------+ +--------v--------+ +--------v--------+
| oauth.json | | .env | | docker compose |
| { | | ANTHROPIC_ | | down/up gateway |
| "anthropic": { | | OAUTH_TOKEN= | | (reloads env) |
| "access":..., | | "sk-ant-oat01-" | +---------+--------+
| "refresh":...,| +-----------------+ |
| "expires":... | +----------v----------+
| } | | OpenClaw Gateway |
| } | | (fresh token loaded |
+--------+----------+ | from container env) |
| +----------+----------+
| mergeOAuthFileIntoStore() |
| (reads on startup) |
+-------------------->+ |
| +--------v---------+
+------------->| api.anthropic.com|
| Claude Opus 4.6 |
+------------------+
```
## Volume Mounts (Docker)
```
HOST PATH CONTAINER PATH
========= ==============
Gateway container (openclaw-openclaw-gateway-1):
/root/.openclaw/ -> /home/node/.openclaw/
/root/.openclaw/credentials/oauth.json -> /home/node/.openclaw/credentials/oauth.json
/root/.openclaw/agents/*/agent/auth-profiles.json -> /home/node/.openclaw/agents/*/agent/auth-profiles.json
/home/node/.claude/ -> /home/node/.claude/
/root/openclaw/.env -> loaded as container env vars (at creation time only)
Claude CLI container (claude-proxy):
/root/.openclaw/workspaces/workspace-claude-proxy/
config/ -> /root/
config/.claude/.credentials.json -> /root/.claude/.credentials.json
```
## Auth Resolution Order (inside gateway)
When the gateway needs to authenticate with Anthropic:
```
1. resolveApiKeyForProvider("anthropic")
2. -> resolveAuthProfileOrder()
3. -> reads agents/<agent>/agent/auth-profiles.json
4. -> isValidProfile() checks each profile:
5. - type:"api_key" -> requires cred.key
6. - type:"oauth" -> requires cred.access (NOT cred.key!)
7. - type:"token" -> requires cred.token
8. -> If valid profile found: use it
9. -> If no valid profile: resolveEnvApiKey("anthropic")
10. -> Reads ANTHROPIC_OAUTH_TOKEN from container env
11. -> isOAuthToken(key) detects "sk-ant-oat" prefix
12. -> Uses Bearer auth + Claude Code identity headers
13. -> Sends request to api.anthropic.com
On gateway startup:
mergeOAuthFileIntoStore()
-> Reads /home/node/.openclaw/credentials/oauth.json
-> Merges into auth profile store (if profile doesn't exist)
```
## Why down/up and NOT restart
```
docker compose restart openclaw-gateway
-> Sends SIGTERM to container process
-> Restarts the SAME container (same env vars from creation time)
-> .env changes are NOT reloaded
-> Result: gateway still has OLD token
docker compose down openclaw-gateway && docker compose up -d openclaw-gateway
-> Stops and REMOVES the container
-> Creates a NEW container (reads .env fresh)
-> New env vars are loaded
-> Result: gateway has NEW token
```
## Source Code References (inside gateway container)
| File | Line | Function |
|------|------|----------|
| `/app/dist/paths-CyR9Pa1R.js` | 190 | `OAUTH_FILENAME = "oauth.json"` |
| `/app/dist/paths-CyR9Pa1R.js` | 198-204 | `resolveOAuthDir()` -> `$STATE_DIR/credentials/` |
| `/app/dist/paths-CyR9Pa1R.js` | 203 | `resolveOAuthPath()` -> joins dir + filename |
| `/app/dist/model-auth-CmUeBbp-.js` | 3048 | `mergeOAuthFileIntoStore()` -- reads oauth.json |
| `/app/dist/model-auth-CmUeBbp-.js` | 3358 | `buildOAuthApiKey()` -- returns `credentials.access` |
| `/app/dist/model-auth-CmUeBbp-.js` | 3832 | `isValidProfile()` -- for oauth, checks `cred.access` |
| `/app/dist/model-auth-CmUeBbp-.js` | 3942 | `resolveApiKeyForProvider()` -- profiles then env fallback |
| `/app/dist/model-auth-CmUeBbp-.js` | 4023 | `resolveEnvApiKey("anthropic")` -> reads env var |