feat(auth): Clerk OAuth login flow + refresh tokens#82
Draft
cpoepke wants to merge 2 commits into
Draft
Conversation
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.
c059ddd to
1755279
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_TOKENpaste with a/dash0-agent-plugin:loginslash command that runs OAuth 2.0 + PKCE against Clerk (Dash0's identity provider).What's in scope
/dash0-agent-plugin:loginslash 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), mode0600.https://clerk.dash0.com(-dev.comauto-selected whenDASH0_OTLP_URLpoints at a dev host). Discovery via RFC 8414 metadata. PKCE S256.auth_*token via CPAPOST /public/ui/organization/auth-tokens. Falls back to the short-lived OAuth access_token when mint fails.offline_accessscope yields a refresh token. SessionStart hook auto-refreshes on 401 and persists the rotated tokens.dash0: connected as <org>(or one of: not configured, not authenticated, auth rejected). The "needs login" branches put a Skill-tool nudge intoadditionalContextso Claude proactively invokes/dash0-agent-plugin:loginbefore continuing with the user's request.client_id+ bound loopback port inclients.jsonso repeat logins reuse the sameredirect_uri(Dash0/Clerk enforce exact match).Region cleanup
The previous prototype carried a
us/eu/devregion selector that turned out not to match Dash0's real URL topology (regional APIs live atapi.<region>.<provider>.dash0.com, OAuth lives at the Clerk frontend API). This PR replaces the region map with URL-only configuration:internal/auth/regions.godeletedCredentialsdropsRegion/APIBase; gainsAuthURL,ClientID,RefreshToken--regionflag removed; replaced by--auth-url,--client-id,--scopeclients.jsonkeyed by AuthURL (not region ID)Configuration
New
userConfigentries (visible in/plugin → Configure):AUTH_URLhttps://clerk.dash0.com. Sniffed asclerk.dash0-dev.comwhen OTLP_URL is dev.OAUTH_CLIENT_IDPlus existing env-var fallbacks:
DASH0_AUTH_URL,DASH0_OAUTH_CLIENT_ID.The Clerk OAuth provider mode IS active at
clerk.dash0.comand 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:Public clientis OFF — Clerk treats the app as confidential and demands aclient_secreton token exchange. CLI/native apps must be public clients (PKCE replaces the secret). Without flipping this on, thePOST /oauth/tokencall returns 401.redirect_uriagainst the app's allowlist. With an empty allowlist, every authorize call is rejected. We need either:http://localhostwildcard registered (Clerk Pro plans), orhttp://localhost:60847/callback) — the plugin already persists the bound port inclients.jsonso 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:
POST /public/ui/organization/auth-tokens) usesCheckUserAuthAndExpectAdminRoleInOrganization→CheckUserAuth, which decodes Bearer tokens as JWT only (nodash0_at_*/auth_*path). This is why we route OAuth through Clerk (issues real JWTs) rather than the regional Dash0 OAuth server (which issues opaquedash0_at_*). If Clerk OAuth ever turns out not to be viable, the alternate fix is a backend PR adding a route that acceptsdash0_at_*and mintsauth_*viaCheckAuth(the universal middleware).Test plan
go test ./...(160 unit tests)go test -tags=e2e -run TestAuthFlow ./test/e2e/(14 scenarios)go vet ./...clean,gofmt -l .clean