@baukit/auth-node

中文文档
0.18.3发布于 2026/11/07MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 8160

Node OIDC device authorization with S256 PKCE, refresh rotation, and a locked token cache.

原始使用文档
文档目录7 节
  1. Device-flow client
  2. Endpoint policy
  3. API origins
  4. Keycloak test helpers
  5. Cache contract
  6. Display-only claims
  7. Migration from product-local auth

@baukit/auth-node

@baukit/auth-node is the Node 24 OIDC device-flow package for CLI and MCP clients. It handles discovery, RFC 8628 polling, S256 PKCE, refresh rotation, and a local profile cache. It has no runtime dependencies.

The package does not print instructions or open a browser. Supply callbacks for those actions.

Device-flow client

import { DeviceFlowClient } from '@baukit/auth-node/device-flow';

const auth = new DeviceFlowClient(
  {
    issuer: process.env['OIDC_ISSUER'] ?? '',
    clientId: process.env['OIDC_CLIENT_ID'] ?? '',
    scopes: ['openid', 'profile', 'offline_access'],
    audience: 'notes-api',
    cache: {
      namespace: 'notes-mcp',
    },
  },
  {
    environmentToken: () => process.env['NOTES_API_TOKEN'],
  },
);

await auth.login({
  presentation: {
    showVerification: ({ verificationUri, userCode }) => {
      process.stderr.write(`Open ${verificationUri}\nEnter ${userCode}\n`);
    },
    showStatus: (status) => {
      process.stderr.write(`Login status: ${status}\n`);
    },
    openBrowser: (url) => openProductBrowser(url),
  },
});

const token = await auth.accessToken();

accessToken() checks the injected environment-token source first. It then reads the selected cache profile and refreshes near-expiry tokens. Refreshes share one promise in a process and hold an adjacent .lock file while reading and replacing the cache.

refreshLeewaySeconds defaults to 60 seconds; the refresh window is the smaller of that value and half the token lifetime, measured as expiresAt - issuedAt.

Pass an AbortSignal to login, accessToken, or logout. Discovery and token requests have a 15-second default timeout. A login has a 10-minute total timeout. Both limits are configurable.

Endpoint policy

The configured issuer must match discovery metadata. Put known issuer aliases in endpointPolicy.issuerAllowlist. Token and device endpoints must share the issuer origin and path. Put a provider's documented endpoint origin in endpointOriginAllowlist when it uses a separate origin.

All issuer, device, token, and verification URLs require HTTPS. Local development can set allowLoopbackHttp: true; this permits only localhost, 127.0.0.0/8, and ::1.

Discovery and token bodies are limited to 64 KiB by default. Errors contain a stable code, an allowlisted message, an optional HTTP status, and no provider body. The client does not log.

API origins

parseApiOrigin(value, { allowLoopbackHttp, label }) checks a configured API base URL, such as an MCP server's PRODUCT_API_URL, with the same scheme rule as the endpoint policy. It trims whitespace, accepts one trailing slash, and returns url.origin.

import { parseApiOrigin } from '@baukit/auth-node';

const apiUrl = parseApiOrigin(process.env['PRODUCT_API_URL'] ?? 'http://localhost:8080', {
  allowLoopbackHttp: process.env['NODE_ENV'] !== 'production',
  label: 'PRODUCT_API_URL',
});

It throws ApiOriginError, a TypeError, with a reason of invalid_url, insecure_scheme, or not_an_origin. Credentials, a path, a query, or a fragment count as not_an_origin. allowLoopbackHttp defaults to false. The message names the label and the broken rule, never the value, so it is safe to log.

Keycloak test helpers

@baukit/auth-node/keycloak-testing sets up users for end-to-end tests against a disposable development Keycloak. It runs on Node 24, uses the global fetch, and needs no Playwright dependency.

The subpath ships as ESM only, like every Baukit package. Node 24 loads it from CommonJS through require(esm), so a CommonJS test project needs no dynamic import(). TypeScript models that only with "module": "node20" or "nodenext"; under "node16" it reports TS1479 on the import, because node16 describes a Node release that could not require ESM.

import {
  allowKeycloakWebOrigin,
  createKeycloakTestUser,
  keycloakStack,
  signInWithKeycloak,
} from '@baukit/auth-node/keycloak-testing';

const stack = keycloakStack({
  url: 'http://localhost:8081',
  realm: 'notes',
  webClientId: 'notes-web',
});

await allowKeycloakWebOrigin(stack, 'http://localhost:5183');
const user = await createKeycloakTestUser(stack);
await page.getByRole('button', { name: 'Sign in' }).click();
await signInWithKeycloak(page, user, stack);

keycloakStack(defaults, environment = process.env) returns the product's defaults unless E2E_KEYCLOAK_URL, E2E_KEYCLOAK_REALM, E2E_KEYCLOAK_ADMIN_USERNAME, E2E_KEYCLOAK_ADMIN_PASSWORD, or E2E_KEYCLOAK_WEB_CLIENT_ID is set. The admin credentials default to admin and admin, and trailing slashes are removed from the URL.

createKeycloakTestUser(stack, user?, options?) signs in to the master realm's admin-cli and creates a verified, enabled user with a permanent password. Without user, the username is e2e-<uuid> and the password is random, so parallel tests never share an identity. Pass username, email, or password when the realm signs in by email. It returns { username, password, email, subject }; subject is the Keycloak user ID, which becomes the sub claim.

revokeKeycloakUserSessions(stack, subject) ends every session of the user, so the app's next refresh fails. allowKeycloakWebOrigin(stack, origin) adds <origin>/* to the web client's redirect URIs and to its post.logout.redirect.uris attribute, and origin to its web origins. It keeps the client's other attributes, and does nothing when all three are present. A post-logout list that holds + already reuses the redirect URIs, so the helper leaves it alone. signInWithKeycloak(page, user, stack, { timeoutMs }) waits until the page is on the Keycloak origin, fills #username and #password, and clicks #kc-login; those IDs do not change with the login locale. page is any object with Playwright's waitForURL and locator, such as a Page or a sign-in popup.

Every admin call takes { fetch, timeoutMs }; the timeout defaults to DEFAULT_KEYCLOAK_REQUEST_TIMEOUT_MS (30 seconds). A failure throws an Error that names the step and HTTP status and never includes a response body or the admin password. Created users and added origins stay in the realm, so point the helpers only at a development realm.

Cache contract

The JSON cache holds named profiles under one namespace. defaultTokenCachePath(namespace) resolves to $XDG_CONFIG_HOME/<namespace>/tokens.json, or ~/.config/<namespace>/tokens.json when XDG_CONFIG_HOME is unset.

Profiles record issuedAt and expiresAt in epoch milliseconds; a profile without issuedAt fails with cache_corrupt.

On POSIX systems the cache file must have mode 0600 and its immediate directory must have mode 0700. Existing unsafe permissions fail with cache_permission. The cache rejects symlink path components. Writes use a new temporary file, sync it, and atomically rename it, so a write failure before rename leaves the old file intact. Windows skips POSIX mode checks and relies on host ACLs.

Use a separate namespace or profile for accounts that must not share credentials. logout() removes the selected profile and leaves other profiles in place.

Display-only claims

displayClaims() and decodeDisplayOnlyClaims() decode a small allowlist of JWT fields without checking the signature. Use the result only for labels such as a whoami display. Never use it for authorization, storage partitions, audit identity, or analytics identity.

Migration from product-local auth

Replace local discovery, device polling, refresh, and cache functions with one DeviceFlowClient. Keep these product inputs in the application:

  • environment variable names and defaults;
  • issuer, client ID, scopes, and audience;
  • CLI text and browser-launch behavior;
  • API base URL and bearer-token wiring; and
  • cache namespace, path, and profile selection.

版本与发布记录

@baukit/auth-node 共发布 62 个版本,首次发布于 2026/09/05;过去 12 个月发布了 62 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.18.3latest2026/11/07今天
0.18.22026/11/061 天前
0.18.12026/11/062 天前
0.18.02026/11/052 天前
0.17.12026/11/034 天前
0.17.02026/11/025 天前

关于 @baukit/auth-node 的常见问题

@baukit/auth-node 是什么?

Node OIDC device authorization with S256 PKCE, refresh rotation, and a locked token cache.

如何安装 @baukit/auth-node?

在项目目录运行 npm install @baukit/auth-node,或使用 pnpm add / yarn add / bun add。

@baukit/auth-node 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@baukit/auth-node 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@baukit/auth-node 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@baukit/auth-node 可以商用吗?

@baukit/auth-node 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@baukit/auth-node 的下载量有多少?

根据 npm 官方下载统计,@baukit/auth-node 在 2026/10/08 – 2026/11/06 这 30 天内被下载 8,160 次,日均约 272 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@baukit/auth-node 还在维护吗?

最新版本 0.18.3 发布于 2026/11/07(今天);至今共发布 62 个版本,其中过去 12 个月发布了 62 个;首次发布于 2026/09/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。