Skip to content

Add alchemy auth command for browser-based login#30

Open
lohnim wants to merge 5 commits intomainfrom
chris/auth
Open

Add alchemy auth command for browser-based login#30
lohnim wants to merge 5 commits intomainfrom
chris/auth

Conversation

@lohnim
Copy link
Collaborator

@lohnim lohnim commented Mar 24, 2026

Summary

  • Add alchemy auth command with browser-based OAuth authorization code flow
  • CLI starts localhost HTTP server, opens browser, receives auth code, exchanges for token via backchannel
  • Supports alchemy auth login (default), alchemy auth status, alchemy auth logout
  • Client sends expires_in_seconds (default 90 days) — server controls the cap
  • withAuthRetry() helper for automatic 401 re-authentication
  • adminClientFromFlags() falls back to auth token when no access key is set

How it works

$ alchemy auth
  ◆ Alchemy Authentication
  Opening browser to log in...
  Waiting for authentication...
  ✓ Logged in successfully
  Token saved to ~/.config/alchemy/config.json
  Expires: 2026-06-22T...
  1. CLI binds HTTP server on localhost:16424
  2. Opens browser to https://auth.alchemy.com/login?redirectUrl=http://localhost:16424/callback
  3. User logs in → authchemy redirects with ?code=<code>
  4. CLI exchanges code for token via POST /api/cli/token { code, redirect_uri, expires_in_seconds }
  5. Server returns { authToken, expiresAt, expiresInSeconds }
  6. CLI saves to ~/.config/alchemy/config.json with 0600 permissions

New files

  • src/lib/auth.ts — HTTP callback server, browser open, code exchange
  • src/commands/auth.tsalchemy auth command (login/status/logout)
  • src/lib/auth-retry.tswithAuthRetry() for automatic 401 re-auth

Modified files

  • src/lib/config.tsauth_token + auth_token_expires_at fields
  • src/lib/resolve.tsresolveAuthToken(), auth token fallback in adminClientFromFlags()
  • src/lib/errors.ts — Updated errAuthRequired() hint
  • src/lib/onboarding.tsauth_token as valid setup method
  • src/index.ts — Register auth command

Companion PR

Test plan

  • Run alchemy auth — verify browser opens and token saved
  • Run alchemy auth status — verify authenticated state
  • Run alchemy auth logout — verify token cleared
  • Verify port-in-use error message
  • Verify --json output modes

@lohnim lohnim requested a review from a team as a code owner March 24, 2026 15:50
@lohnim lohnim mentioned this pull request Mar 25, 2026
3 tasks
@lohnim lohnim force-pushed the chris/auth branch 2 times, most recently from 4fb8611 to f2676aa Compare March 25, 2026 14:46
lohnim and others added 4 commits March 26, 2026 11:02
Browser-based CLI authentication via localhost callback with PKCE
(Proof Key for Code Exchange) to bind auth codes to the originating
CLI instance.

- `alchemy auth` / `alchemy auth login` — browser login (skips if valid token exists)
- `alchemy auth login --force` — force re-authentication
- `alchemy auth status` — show auth state
- `alchemy auth logout` — revoke server-side + clear local token

1. Generate PKCE code_verifier/code_challenge
2. Open browser to authchemy with challenge in URL
3. Receive auth code via localhost:16424/callback
4. Exchange code + verifier via POST /api/cli/token
5. Save token + expiresAt to ~/.config/alchemy/config.json

- performBrowserLogin() shared between login command and auth-retry
- withAuthRetry() helper for automatic 401 re-authentication
- resolveAuthToken() with expiry check (single source of truth)
- adminClientFromFlags() falls back to auth token when no access key
- auth_token recognized as valid setup method in onboarding
- Server-returned expiresAt used instead of client-side computation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ing or encoding

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Comment on lines +14 to +16
const SUCCESS_HTML = `<!DOCTYPE html>
<html>
<head><title>Alchemy CLI</title>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we pull this out to a separate file?

}
});

server.listen(port);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we bind explicitly to 127.0.0.1 here?

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.

2 participants