Skip to content

feat(cli-auth): add @clerk/cli-auth package#8642

Draft
nicolas-angelo wants to merge 20 commits into
mainfrom
feat/cli-auth
Draft

feat(cli-auth): add @clerk/cli-auth package#8642
nicolas-angelo wants to merge 20 commits into
mainfrom
feat/cli-auth

Conversation

@nicolas-angelo
Copy link
Copy Markdown

@nicolas-angelo nicolas-angelo commented May 26, 2026

Summary

Adds @clerk/cli-auth to the monorepo — a package for adding Clerk authentication to Node.js CLIs and the backend routes those CLIs talk to.

Ships two entry points:

  • @clerk/cli-auth — CLI runtime: browser sign-in via PKCE + localhost callback, token storage, refresh, revocation, credential resolution.
  • @clerk/cli-auth/server — backend route handlers for verifying CLI bearers via clerk.authenticateRequest. Drop into any framework speaking Web Request/Response (Next.js App Router, Hono, Cloudflare Workers, Bun, Deno).

CLI setup

ClerkCliAuth

import { ClerkCliAuth } from '@clerk/cli-auth';

const auth = new ClerkCliAuth({
  clientId: process.env.CLERK_OAUTH_CLIENT_ID!,
  issuer: process.env.CLERK_ISSUER!,
  scopes: ['profile', 'email', 'openid', 'offline_access'],
  storage: 'keychain', // or 'file', or 'memory'
  // Optional: enables auth.verifyToken() against your backend
  identityEndpoint: 'https://myapp.com/api/cli/identity',
  // Optional: the environment variable that resolveToken() reads from
  tokenEnvVar: 'MYAPP_API_KEY',
});

Usage examples

// Sign in: opens browser, runs localhost callback, exchanges code, stores tokens
const { tokens, user } = await auth.login();

// Get a valid access token (auto-refreshes within 30s of expiry)
const token = await auth.getAccessToken();

// Fresh whoami against /oauth/userinfo
const me = await auth.whoami();

// Resolve whichever token is available from auth
// source: 'arg' (from --token), 'env' (from tokenEnvVar), or 'oauth' (from login)
const { token, source } = await auth.resolveToken({ tokenFromArg: argv.token });

// Revoke at the issuer, then clear local credentials
await auth.logout();
// Local logout only (skip token revocation)
await auth.logout({ revoke: false })

Identity

The SDK provides helper methods for use cases that require resolving identity from an auth token — for example, a whoami command that works with both OAuth and API key auth. All related utilities conform to the Identity interface, covering four of Clerk's subject types:

type Identity = UserIdentity | OrgIdentity | MachineIdentity | ScimIdentity;
// narrow w/ type guards
// isUserIdentity / isOrgIdentity / isMachineIdentity / isScimIdentity 

cliAuth

Returns an instance with helper methods that can be used to resolve auth info and secure endpoints that your CLI communicates with.

// lib/clerk-cli.ts
import { cliAuth } from '@clerk/cli-auth/server';
import { clerkClient } from '@clerk/nextjs/server';

export const auth = cliAuth()
// or optionally pass in a pre-configured clerk client
export const auth = cliAuth({ client: clerkClient });

Identity resolution handler (for tokens)

The CLI can fetch the authorized party's identity by calling a backend endpoint you host.

For convenience, the SDK exports handle(), which takes a cliAuth instance and returns a route handler that verifies the bearer token on the Authorization header, resolves the identity of it's subject and responds with a JSON Identity payload.

Drop into any Fetch API compatible framework (Next.js App Router, Hono, Cloudflare Workers, Bun, Deno).

// app/api/cli/identity/route.ts
import { handle } from '@clerk/cli-auth/server';
import { auth } from '@/lib/clerk-cli';

export const GET = handle({ auth, accepts: ['api_key', 'oauth_token'] });

Don't forget to point identityEndpoint (in your ClerkCliAuth configuration) at this URL to opt-in.

Accepted credentials

accepts value Matches
'api_key' ak_* Clerk API keys with user, org, or machine subjects
'oauth_token' oat_* opaque OAuth access tokens or JWTs
'any' Either of the above

The SDK's verification helpers do not support session JWTs and M2M tokens. To enable authorization for machine identities, mint an API key with a mch_* subject.

For other routes your CLI communicates with use auth.verifyTokenFromRequest() directly inside your handler:

// app/api/cli/projects/route.ts
import { NextResponse } from 'next/server';
import { auth } from '@/lib/clerk-cli';

export async function GET(request: Request) {
  const tokenInfo = await auth.verifyTokenFromRequest(request, {
    accepts: ['api_key', 'oauth_token'],
  });

  const projects = await db.projects.findMany({
    where: { ownerId: tokenInfo.subject },
  });

  return NextResponse.json({ projects });
}

verifyTokenFromRequest throws if the header is missing, the token is invalid, or its type isn't accepted.

The returned tokenInfo has subject, type, scopes, and claims — use any of them to authorize the request.

Test plan

  • Unit tests pass — pnpm test (20 tests, runs offline)
  • Integration suite passes — pnpm test:integration (16 tests, requires CLERK_SECRET_KEY and CLERK_PUBLISHABLE_KEY; skips if unset)
  • Integration suite provisions throwaway Clerk resources (user, org, api keys, etc) and tears them all down in afterAll
  • pnpm build, pnpm lint, pnpm lint:attw, pnpm lint:publint success

@vercel
Copy link
Copy Markdown

vercel Bot commented May 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment May 29, 2026 2:25am

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 26, 2026

🦋 Changeset detected

Latest commit: c39913e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clerk/cli-auth Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

const DEFAULT_SCOPES: OAuthScope[] = ['profile', 'email', 'openid', 'offline_access'];

function normalizeIssuer(issuer: string): string {
const normalized = issuer.trim().replace(/\/+$/, '');
Comment thread packages/cli-auth/src/lib/token-exchange.ts Fixed
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This pull request introduces @clerk/cli-auth, a new reusable package for adding OAuth 2.0 + PKCE authentication to Node.js CLI applications. The package includes a localhost callback server for browser-based sign-in, configurable credential storage backends (OS keychain with file fallback, file, or in-memory), automatic token refresh and revocation, userinfo lookup, and optional API key verification. All public types, error codes, and helper functions are properly exported, and the implementation is comprehensively tested with Vitest.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a new @clerk/cli-auth package to the monorepo. It is clear, specific, and directly reflects the primary objective of the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description comprehensively details the new @clerk/cli-auth package, its two entry points, CLI setup, usage examples, identity resolution, and test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nicolas-angelo nicolas-angelo requested a review from Railly May 26, 2026 13:38
}

const entry = new keyring.Entry(this.service, this.account(key));
return entry.getPassword() ?? (await this.fallback.get(key));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fallback can return stale file credentials after keychain was the successful write path. set() only writes to keychain when keychain works, but get() falls back to file on null/failure, so an old fallback token can be resurrected. Can we either pick one active backend after probing, or mirror writes/deletes into the fallback so both stores stay coherent?

async function requestTokens(issuer: string, body: URLSearchParams): Promise<TokenSet> {
let response: Response;
try {
response = await fetch(endpoint(issuer, '/oauth/token'), {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This package now has several raw fetch calls without a timeout, here and in verify-api-key.ts. The callback server timeout does not cover token exchange, refresh, revoke, userinfo, or API-key verification, so CLI commands can hang indefinitely. Can we route these through one small internal HTTP helper with an AbortController, shared body parsing, and consistent error mapping?

Comment thread packages/cli-auth/src/clerk-cli-auth.ts Outdated

async whoami(): Promise<UserInfo | null> {
const cachedUser = await this.getJson<UserInfo>('user');
if (cachedUser) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

whoami() returns cached user data before checking whether the stored access token is still fresh or refreshable. That makes identity freshness ambiguous after expiry, revocation, or scope/org changes.

@nicolas-angelo nicolas-angelo marked this pull request as draft May 26, 2026 16:46
…es.create + correct apiKeys.verify signature
…d stabilize verifier on clerk.authenticateRequest
}

function endpoint(issuer: string, path: string): string {
return `${issuer.replace(/\/+$/, '')}${path}`;
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.

3 participants