Skip to content

feat(auth): Clerk OAuth login flow + refresh tokens#82

Draft
cpoepke wants to merge 2 commits into
mainfrom
claude/add-oauth-auth-Xy6iv
Draft

feat(auth): Clerk OAuth login flow + refresh tokens#82
cpoepke wants to merge 2 commits into
mainfrom
claude/add-oauth-auth-Xy6iv

Conversation

@cpoepke

@cpoepke cpoepke commented May 17, 2026

Copy link
Copy Markdown
Contributor

⚠️ DRAFT — pending Clerk OAuth app configuration before this can be marked ready.

Summary

Adds a browser-based sign-in flow for the Dash0 Claude Code plugin so users can authenticate to Dash0 once and have the plugin emit telemetry on their behalf. Replaces manual AUTH_TOKEN paste with a /dash0-agent-plugin:login slash command that runs OAuth 2.0 + PKCE against Clerk (Dash0's identity provider).

What's in scope

  • /dash0-agent-plugin:login slash command — opens the browser, completes PKCE, persists credentials to ~/Library/Application Support/dash0/credentials.json (macOS) / $XDG_CONFIG_HOME/dash0/credentials.json (Linux) / %AppData%\dash0\credentials.json (Windows), mode 0600.
  • Clerk-based OAuth — defaults to https://clerk.dash0.com (-dev.com auto-selected when DASH0_OTLP_URL points at a dev host). Discovery via RFC 8414 metadata. PKCE S256.
  • Long-lived token mint — after token exchange the plugin uses the Clerk JWT to mint an auth_* token via CPA POST /public/ui/organization/auth-tokens. Falls back to the short-lived OAuth access_token when mint fails.
  • Refresh-token rotationoffline_access scope yields a refresh token. SessionStart hook auto-refreshes on 401 and persists the rotated tokens.
  • SessionStart status — hook emits dash0: connected as <org> (or one of: not configured, not authenticated, auth rejected). The "needs login" branches put a Skill-tool nudge into additionalContext so Claude proactively invokes /dash0-agent-plugin:login before continuing with the user's request.
  • Per-user OAuth client registration caches client_id + bound loopback port in clients.json so repeat logins reuse the same redirect_uri (Dash0/Clerk enforce exact match).

Region cleanup

The previous prototype carried a us/eu/dev region selector that turned out not to match Dash0's real URL topology (regional APIs live at api.<region>.<provider>.dash0.com, OAuth lives at the Clerk frontend API). This PR replaces the region map with URL-only configuration:

  • internal/auth/regions.go deleted
  • Credentials drops Region/APIBase; gains AuthURL, ClientID, RefreshToken
  • --region flag removed; replaced by --auth-url, --client-id, --scope
  • clients.json keyed by AuthURL (not region ID)

Configuration

New userConfig entries (visible in /plugin → Configure):

Key Notes
AUTH_URL Override OAuth host. Default: https://clerk.dash0.com. Sniffed as clerk.dash0-dev.com when OTLP_URL is dev.
OAUTH_CLIENT_ID Pre-registered Clerk OAuth app client_id (Clerk doesn't expose Dynamic Client Registration).

Plus existing env-var fallbacks: DASH0_AUTH_URL, DASH0_OAUTH_CLIENT_ID.

⚠️ Known issue blocking real-world login (why this is a draft)

The Clerk OAuth provider mode IS active at clerk.dash0.com and the dev Clerk tenant (included-camel-2.clerk.accounts.dev) — discovery + JWKS + PKCE are all served correctly. However, the dev OAuth app currently has two settings that prevent the flow from completing:

  1. Public client is OFF — Clerk treats the app as confidential and demands a client_secret on token exchange. CLI/native apps must be public clients (PKCE replaces the secret). Without flipping this on, the POST /oauth/token call returns 401.
  2. No Redirect URIs registered — Clerk validates redirect_uri against the app's allowlist. With an empty allowlist, every authorize call is rejected. We need either:
    • http://localhost wildcard registered (Clerk Pro plans), or
    • One specific port (e.g. http://localhost:60847/callback) — the plugin already persists the bound port in clients.json so repeat logins reuse it.

Both are dashboard-only changes, no code change needed. Once the dev app is reconfigured (and a prod Clerk OAuth app is created with the same shape), this PR can be marked ready.

Also relevant:

  • CPA mint endpoint (POST /public/ui/organization/auth-tokens) uses CheckUserAuthAndExpectAdminRoleInOrganizationCheckUserAuth, which decodes Bearer tokens as JWT only (no dash0_at_* / auth_* path). This is why we route OAuth through Clerk (issues real JWTs) rather than the regional Dash0 OAuth server (which issues opaque dash0_at_*). If Clerk OAuth ever turns out not to be viable, the alternate fix is a backend PR adding a route that accepts dash0_at_* and mints auth_* via CheckAuth (the universal middleware).

Test plan

  • Unit tests: go test ./... (160 unit tests)
  • E2E tests against in-process mock Clerk-shaped server: go test -tags=e2e -run TestAuthFlow ./test/e2e/ (14 scenarios)
  • go vet ./... clean, gofmt -l . clean
  • Manual local dev: login flow reaches browser, OAuth completes, token exchange succeeds (blocked on Clerk app misconfig described above)
  • Manual live test against prod Clerk once OAuth app is created

@cpoepke cpoepke changed the title feat: add OAuth PKCE login flow + region selector feat: add /dash0-agent-plugin:login OAuth PKCE flow May 17, 2026
@cpoepke cpoepke marked this pull request as draft May 17, 2026 22:33
@cpoepke cpoepke changed the title feat: add /dash0-agent-plugin:login OAuth PKCE flow feat(auth): Clerk OAuth login flow + refresh tokens May 17, 2026
cpoepke added 2 commits May 18, 2026 00:39
Adds a browser-based sign-in flow so users can authenticate the Dash0
Claude Code plugin once and have it emit telemetry on their behalf.

- /dash0-agent-plugin:login slash command runs OAuth 2.0 + PKCE against
  Clerk (Dash0's identity provider). Discovery via RFC 8414 metadata,
  PKCE S256. Defaults to https://clerk.dash0.com; auto-selects the
  dev tenant when DASH0_OTLP_URL points at a .dash0-dev.com host.

- Credentials are persisted to the OS user-config dir (mode 0600):
    ~/Library/Application Support/dash0/credentials.json (macOS)
    $XDG_CONFIG_HOME/dash0/credentials.json (Linux)
    %AppData%\dash0\credentials.json (Windows)

- After OAuth, the plugin uses the issued Clerk JWT to mint a long-lived
  auth_* token via CPA POST /public/ui/organization/auth-tokens. Falls
  back to the short-lived OAuth access_token when mint fails (e.g. dev
  envs without admin role).

- Refresh-token rotation: offline_access scope yields a refresh token;
  SessionStart hook auto-refreshes on 401, re-mints, persists, retries
  CheckConnectivity, and reports the rotated state to the user.

- SessionStart printHookResponse always emits a status line:
    - dash0: telemetry is not active        (no OTLP_URL)
    - dash0: not authenticated              (no token)
    - dash0: auth token rejected            (401 after refresh)
    - dash0: connected as <org-id>          (valid)
  The "needs login" branches put a Skill-tool nudge into
  additionalContext so Claude proactively invokes the slash command
  before continuing with the user's request.

- Per-user OAuth client registration caches client_id + the bound
  loopback port in clients.json so repeat logins reuse the same
  redirect_uri (Dash0 / Clerk enforce exact match).

- New userConfig entries: AUTH_URL, OAUTH_CLIENT_ID (plus env-var
  fallbacks DASH0_AUTH_URL, DASH0_OAUTH_CLIENT_ID).

- Drops the original region selector (us / eu / dev) and the
  DASH0_REGION env var — the URL-only model matches Dash0's real
  topology (Clerk for OAuth, CPA for mint, regional API for ingest).
  Credentials now carry AuthURL, ClientID, RefreshToken instead of
  Region/APIBase. Clients map keyed by AuthURL.

- CI adds an auth-e2e job that runs the mocked flow without an
  ANTHROPIC_API_KEY secret (works for fork PRs).

- 14 e2e scenarios + 160 unit tests cover discover/register/exchange/
  mint/refresh paths against an in-process mock Clerk-shaped server.
Pure formatting (struct-tag alignment). No behavior changes.
@cpoepke cpoepke force-pushed the claude/add-oauth-auth-Xy6iv branch from c059ddd to 1755279 Compare May 17, 2026 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant