# @codefox-inc/oauth-provider 中文文档

规范地址：https://npmcn.com/package/%40codefox-inc/oauth-provider/

版本：0\.4\.2
npm 元数据更新时间：2026-05-17T09:19:09.537Z
中文译文更新时间：2026-11-10T06:41:26.900Z
软件包许可：Apache-2\.0

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40codefox-inc/oauth-provider
原文对照：https://npmcn.com/package/%40codefox-inc/oauth-provider/?lang=original
项目主页：https://github.com/codefox-inc/convex-oauth-provider#readme
源码仓库：https://github.com/codefox-inc/convex-oauth-provider

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：865 kB，116 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：2 个
- peer 依赖：convex ^1\.31\.6，react ^18\.2\.0 \|\| ^19\.0\.0
- 首次发布：2026-01-23T15:16:45.879Z
- 最新版本发布：2026-05-17T09:19:09.221Z
- 近 30 天下载量：172999（2026-10-10 至 2026-11-08，npm 官方下载统计）
- 已发布版本数：11（近 12 个月 11 个）

## 中文简介

OAuth 2.1 / Convex Auth (Beta) 的 OpenID 连接提供者.

## 安装

```sh
npm install @codefox-inc/oauth-provider
```

也可以使用：`pnpm add @codefox-inc/oauth-provider`、`yarn add @codefox-inc/oauth-provider`、`bun add @codefox-inc/oauth-provider`

## README

# @ codefox- inc/ oauth- 提供者

OAuth 2.1 / OpenID 连接提供器作为Convex组件被执行.

> **QQ Beta 软件** - 生产用在你自己的风险.

测试过 [凸起轴](https://labs.convex.dev/auth) 财务报告和审定财务报表 [@ convex-dev/ better-auth (英语)](https://labs.convex.dev/better-auth)。 。 。 .

## 为什么?

大多数MCP客户端(如克洛德代码或ChatGPT)都要求您的应用程序成为OAuth供应商. 如果您想要将您的 Convex 应用程序连接到 MCP 客户端, 您需要执行 OAuth 2.1.

此组件将您的 Convex 应用程序变成一个完全符合 OAuth 2.1 的提供者,所以您可以:

-   从框中连接 MCP 客户端
-   让客户端通过动态客户端注册自动注册
-   让用户控制每个应用程序的权限
-   专注于您的应用程序, 而非 OAuth 复杂度

## 安装

```bash
bun add @codefox-inc/oauth-provider
```

## 特征

-   **OAuth 2.1 符合规定** 授权和符号终点
-   **OpenID 连接发现** 用于自动客户端配置
-   **需要PKCE** 所有授权代码流量(仅限S256)
-   **RFC 8707 资源指标** 供观众使用
-   **RFC 9068 JWT 访问令牌** (单位:千美元)`typ: at+jwt`, (中文(简体) ), `client_id`, (中文(简体) ), `scope`, (中文(简体) ), `jti`(中文(简体) )
-   **安全信使存储** 使用SHA-256散列作为代币和授权代码
-   **JWT 访问符** 与 RS256 签名
-   **刷新符号旋转** 加强安保
-   **动态客户注册** (选入)
-   **授权管理** 用户同意跟踪
-   **JWKS 结束点** 用于符号验证

**OAuth 2.1 遵约情况**

其后执行 [奥奥特2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1) 及相关的OAuth/OIDC规格:

### 支持的赠款类型

-   ✅ **带有 PKCE 的授权代码** (公共和保密客户)
-   ✅ **刷新托肯** (带有符号旋转)

### 不支持的地物( OAuth 2. 0 遗产)

-   ❌ **间接赠款** (因安全原因在OAuth 2.1被拆除)
-   ❌ **资源所有人密码证书赠款** (在OAuth 2.1中删除)
-   ❌ **PKCE 平原方法** (每个OAuth2.1最佳做法只支持S256)

### 关键安全要求

-   **PKCE 执行**: 所有授权代码流量需要使用 S256 方法的 PKCE
-   **重定向 URI 验证**: 精确字符串匹配(仅限RFC 8252回转可变端口例外)
-   **资源约束**数字 : `resource` 值与授权授予和刷新令牌绑定
-   **访问托肯观众**: 访问令牌 `aud` 是授权的 `resource`,或配置的默认受众
-   **授权代码**: 单用,10分钟后到期
-   **托肯·哈兴**: 所有以 SHA-256 散列存储的令牌
-   **刷新托肯旋转**: 每次使用时发布新的刷新令牌,旧令牌作废

**安全特性**

### 内设安全控制

-   **PKCE 执行**: 所有授权代码流量都需要PKCE(代码 质疑/代码 验证)
-   **重定向 URI 验证**: 严格核对已注册的 URI
-   **范围验证**:每个客户只允许注册范围
-   **托肯·哈兴**: 访问和刷新令牌作为SHA-256散列存储
-   **客户端秘密搜索**: 使用 bcrypt 的客户机密
-   **客户端机密兼容性**:新发布的符合bcrypt72字节输入限制的客户机密;现有的更长的秘密对于补丁放出兼容性仍然有效,在实用时应当被旋转
-   **内部变异**:关键业务,如 `issueAuthorizationCode` 无法直接访问
-   **默认禁用 DCR**: 必须明确启用动态客户端注册

### 授权流量安全

这个 `/oauth/authorize` 端点进行全面的验证 :

1.  客户身份验证
2.  根据注册的 URI 重定向 URI 匹配
3.  对照客户端允许的范围进行范围验证
4.  PKCE 要求(使用 S256 方法的代码-挑战)
5.  用户认证通过 `getUserId` 钩子

**范围和托肯类型**

### 支持的范围

-   **`openid`**: OpenID 连接认证和ID 令牌需要
-   **`profile`**: 授权访问用户配置信息(名称、图片)
-   **`email`**: 授权访问用户电子邮件地址
-   **`offline_access`**: 为长期访问启用刷新令牌发行

### 刷新托肯要求

刷新符号是 **仅印发** 当 `offline_access` 在最初授权期间,请求并授予范围。 对于 OpenID 连接请求, `offline_access` 需求 `prompt=consent` (或包含 `consent`:

-   ✅ **与 `offline_access`**:客户端同时收到访问令牌和刷新令牌
-   ❌ **无 `offline_access`**:客户端仅接收访问令牌(无刷新令牌)

**刷新托肯赠款流程 :**

-   使用 `refresh_token` 获取新访问符的赠款类型
-   原始授权必须包括: `offline_access` 范围
-   刷新令牌在每次使用时自动旋转(旧令牌无效)
-   新的刷新令牌保持与原值相同的范围
-   重用旋转的刷新令牌将活动刷新的家族及其授权记录取消

这遵循了OAuth 2.1和OpenID Connect的规格,确保了长寿的刷新令牌只有在得到用户明确同意的情况下才发布.

**资源指标和有约束力受众**

这个提供者支持 RFC 8707 `resource` mCP和其他资源服务器流量的指标.

-   `resource` 可在授权请求中选择.
-   如果存在,它必须是绝对的无碎片URI.
-   授权代码存储批准 `resource`。 。 。 .
-   令牌请求可能重复同样内容 `resource`,但不能添加未经批准的新的.
-   刷新令牌在旋转时保留相同的资源/听众绑定 .
-   访问符使用授权 `resource` 作为 `aud`; 否则使用 `applicationID` 默认 `convex` 观众.

对于自定义同意的 UI , 保存收到的 `resource` 参数并传递给 `issueAuthorizationCode`。 。 。 .

## OAuth 托肯检测帮助工具

提供区分 OAuth 令牌和会话令牌的帮助函数 :

```typescript
import { isOAuthToken, getOAuthClientId } from "@codefox-inc/oauth-provider";

const identity = await ctx.auth.getUserIdentity();

if (isOAuthToken(identity)) {
    // Handle OAuth token (MCP client, third-party apps, etc.)
    const clientId = getOAuthClientId(identity);
    console.log("OAuth client:", clientId);
} else {
    // Handle Convex Auth session (first-party user)
}
```

## 设置

### 1\. 设置环境变量

这个组件与任何认证系统相配合. 选择匹配您的堆栈的设置 :

#### 备选案文A:带有 Convex Auth

如果你在用的话 [凸起轴](https://labs.convex.dev/auth),您已经配置了所需的环境变量(`JWT_PRIVATE_KEY`, (中文(简体) ), `JWKS`, (中文(简体) ), `SITE_URL`) (中文(简体) ).

#### 备选案文B:使用更好的认证

如果你在用的话 [@ convex-dev/ better-auth (英语)](https://labs.convex.dev/better-auth),您可以共享相同的密钥 :

```bash
bunx convex env set OAUTH_PRIVATE_KEY "$(cat private.pem)"  # Or use JWT_PRIVATE_KEY
bunx convex env set OAUTH_JWKS '{"keys":[...]}'             # Or use JWKS
bunx convex env set SITE_URL "https://your-app.example.com"
```

**重要内容:** 当使用更好的 Auth 时, 设置 `applicationID: "oauth-provider"` 在 OAuth 配置中区分 OAuth 符号和 Better Auth 会话符号.

**选项 C: 手工设置**

手动生成 RSA 密钥 :

```bash
# Generate private key
openssl genrsa -out private.pem 2048

# Generate JWKS (use https://mkjwk.org or this script)
node -e "
const jose = require('jose');
const fs = require('fs');
const privateKey = fs.readFileSync('private.pem', 'utf8');
(async () => {
  const key = await jose.importPKCS8(privateKey, 'RS256');
  const jwk = await jose.exportJWK(key);
  console.log(JSON.stringify({ keys: [{ ...jwk, use: 'sig', alg: 'RS256', kid: 'default-key' }] }));
})();
"
```

设置环境变量 :

```bash
bunx convex env set JWT_PRIVATE_KEY "-----BEGIN RSA PRIVATE KEY-----\n..."
bunx convex env set JWKS '{"keys":[...]}'
bunx convex env set SITE_URL "https://your-app.example.com"
```

### 2\. 登记部分

```typescript
// convex/convex.config.ts
import { defineApp } from "convex/server";
import oauthProvider from "@codefox-inc/oauth-provider/convex.config";

const app = defineApp();
app.use(oauthProvider, { name: "oauthProvider" });

export default app;
```

### 3\. 配置 HTTP 路由

#### 备选案文A:使用帮助器函数(建议)

```typescript
// convex/http.ts
import { httpAction } from "./_generated/server";
import { httpRouter } from "convex/server";
import { OAuthProvider, registerOAuthRoutes } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";
import { api } from "./_generated/api";

const http = httpRouter();

const oauthProvider = new OAuthProvider(components.oauthProvider, {
    privateKey: process.env.JWT_PRIVATE_KEY!,
    jwks: process.env.JWKS!,
    siteUrl: process.env.SITE_URL!,

    // REQUIRED: Authenticate user for authorization endpoint
    getUserId: async (ctx, request) => {
        const identity = await ctx.auth.getUserIdentity();
        return identity?.subject ?? null;
    },
});

// Register all OAuth routes automatically
registerOAuthRoutes(http, httpAction, oauthProvider, {
    siteUrl: process.env.SITE_URL!,
    // OPTIONAL: Override the prefix used for route registration.
    // By default, this uses oauthProvider's config prefix.
    // prefix: "/oauth",
    getUserProfile: async (ctx, userId) => {
        // Return user profile for /oauth/userinfo endpoint
        const user = await ctx.runQuery(api.users.get, { userId });
        return user ? {
            sub: userId,
            name: user.name,
            email: user.email,
            picture: user.pictureUrl
        } : null;
    },
});

export default http;
```

#### 备选案文B:使用更好的认证

```typescript
// convex/http.ts
import { httpAction } from "./_generated/server";
import { httpRouter } from "convex/server";
import { OAuthProvider, registerOAuthRoutes } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";
import { api } from "./_generated/api";

const http = httpRouter();

const oauthProvider = new OAuthProvider(components.oauthProvider, {
    privateKey: process.env.OAUTH_PRIVATE_KEY ?? process.env.JWT_PRIVATE_KEY!,
    jwks: process.env.OAUTH_JWKS ?? process.env.JWKS!,
    siteUrl: process.env.SITE_URL!,

    // IMPORTANT: Set applicationID to distinguish from Better Auth tokens
    applicationID: "oauth-provider",

    getUserId: async (ctx, request) => {
        const identity = await ctx.auth.getUserIdentity();
        return identity?.subject ?? null;
    },
});

// Register Better Auth routes first (if using @convex-dev/better-auth)
// authComponent.registerRoutes(http, createAuth, { cors: true });

// Then register OAuth routes
registerOAuthRoutes(http, httpAction, oauthProvider, {
    siteUrl: process.env.SITE_URL!,
    getUserProfile: async (ctx, userId) => {
        const user = await ctx.runQuery(api.users.get, { userId });
        return user ? {
            sub: userId,
            name: user.name,
            email: user.email,
            picture: user.pictureUrl
        } : null;
    },
});

export default http;
```

**备选案文C:人工路由登记**

```typescript
// convex/http.ts
import { httpAction } from "./_generated/server";
import { httpRouter } from "convex/server";
import { OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

const http = httpRouter();

const oauthProvider = new OAuthProvider(components.oauthProvider, {
    privateKey: process.env.JWT_PRIVATE_KEY!,
    jwks: process.env.JWKS!,
    siteUrl: process.env.SITE_URL!,

    // REQUIRED: Authenticate user for authorization endpoint
    getUserId: async (ctx, request) => {
        const identity = await ctx.auth.getUserIdentity();
        return identity?.subject ?? null;
    },
});

// OpenID Connect Discovery
http.route({
    path: "/oauth/.well-known/openid-configuration",
    method: "GET",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.openIdConfiguration(ctx, req)
    ),
});

// JWKS endpoint
http.route({
    path: "/oauth/.well-known/jwks.json",
    method: "GET",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.jwks(ctx, req)
    ),
});

// Authorization endpoint (validates and issues auth codes)
http.route({
    path: "/oauth/authorize",
    method: "GET",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.authorize(ctx, req)
    ),
});

// Token endpoint
http.route({
    path: "/oauth/token",
    method: "POST",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.token(ctx, req)
    ),
});

// UserInfo endpoint
http.route({
    path: "/oauth/userinfo",
    method: "GET",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.userInfo(ctx, req, async (userId) => {
            const user = await ctx.runQuery(api.users.get, { userId });
            return user ? { sub: userId, name: user.name, email: user.email } : null;
        })
    ),
});

// Dynamic Client Registration (optional)
http.route({
    path: "/oauth/register",
    method: "POST",
    handler: httpAction((ctx, req) =>
        oauthProvider.handlers.register(ctx, req)
    ),
});

export default http;
```

## 用户信息端点

要求数 `openid` 范围。 根据范围提出的回返申请:

-   `openid`: 总是返回 `sub`
-   `profile`页:1 `name`, (中文(简体) ), `picture`
-   `email`页:1 `email` (和 `email_verified` (如果有)

**客户注册**

### 注册 OAuth 客户端( Admin)

```typescript
// convex/oauthAdmin.ts
import { mutation } from "./_generated/server";
import { OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

export const registerOAuthClient = mutation({
    handler: async (ctx, args: {
        name: string;
        redirectUris: string[];
        scopes: string[];
        type: "confidential" | "public";
    }) => {
        // Check admin permissions
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) throw new Error("Unauthorized");

        const oauthProvider = new OAuthProvider(components.oauthProvider, {
            privateKey: process.env.JWT_PRIVATE_KEY!,
            jwks: process.env.JWKS!,
            siteUrl: process.env.SITE_URL!,
        });

        const result = await oauthProvider.registerClient(ctx, {
            name: args.name,
            redirectUris: args.redirectUris,
            scopes: args.scopes,
            type: args.type,
        });

        // IMPORTANT: Save clientSecret securely - it's only returned once!
        return result;
    },
});
```

## 授权流量

### 自动授权处理器

这个 `/oauth/authorize` 端点自动处理完整的授权流 :

```
GET /oauth/authorize?
  response_type=code
  &client_id=CLIENT_ID
  &redirect_uri=REDIRECT_URI
  &scope=openid+profile+email
  &resource=https://api.example.com/mcp
  &state=STATE
  &code_challenge=CHALLENGE
  &code_challenge_method=S256
  &nonce=NONCE
```

处理者:

1.  验证客户端身份
2.  对照已注册的 URI 检查方向调整 uri
3.  验证请求的范围
4.  需要 PKCE( 代码- 挑战)
5.  验证和装订 `resource` 提供时
6.  通过 `getUserId`
7.  发布授权代码
8.  用代码重定向回客户端

**自定义授权流程( 高级)**

如果您需要自定义同意, 您可以直接使用 SDK 方法 :

```typescript
// convex/oauth.ts
import { mutation } from "./_generated/server";
import { OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

export const approveAuthorization = mutation({
    handler: async (ctx, args: {
        clientId: string;
        scopes: string[];
        redirectUri: string;
        codeChallenge: string;
        codeChallengeMethod: string;
        nonce?: string;
        resource?: string;
    }) => {
        // Verify user is authenticated
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) throw new Error("Not authenticated");

        const oauthProvider = new OAuthProvider(components.oauthProvider, {
            privateKey: process.env.JWT_PRIVATE_KEY!,
            jwks: process.env.JWKS!,
            siteUrl: process.env.SITE_URL!,
        });

        // Issue authorization code (automatically creates authorization record)
        const authCode = await oauthProvider.issueAuthorizationCode(ctx, {
            userId: identity.subject,
            clientId: args.clientId,
            scopes: args.scopes,
            redirectUri: args.redirectUri,
            codeChallenge: args.codeChallenge,
            codeChallengeMethod: args.codeChallengeMethod,
            nonce: args.nonce,
            resource: args.resource,
        });

        return authCode;
    },
});
```

当授权请求包含 `resource`把它显示在同意书上 并且通过它没有改变。 如果象征性请求要求 `resource` 未存储在授权代码中, 符号端点返回 `invalid_target`。 。 。 .

**授权管理**

### 列表用户授权的应用程序

```typescript
import { query } from "./_generated/server";
import { OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

export const listAuthorizedApps = query({
    handler: async (ctx) => {
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) return [];

        const oauthProvider = new OAuthProvider(components.oauthProvider, {
            privateKey: process.env.JWT_PRIVATE_KEY!,
            jwks: process.env.JWKS!,
            siteUrl: process.env.SITE_URL!,
        });

        return await oauthProvider.listUserAuthorizations(ctx, identity.subject);
    },
});
```

### 撤销授权

```typescript
import { mutation } from "./_generated/server";
import { OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

export const revokeApp = mutation({
    handler: async (ctx, args: { clientId: string }) => {
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) throw new Error("Not authenticated");

        const oauthProvider = new OAuthProvider(components.oauthProvider, {
            privateKey: process.env.JWT_PRIVATE_KEY!,
            jwks: process.env.JWKS!,
            siteUrl: process.env.SITE_URL!,
        });

        // Deletes authorization and all associated tokens
        await oauthProvider.revokeAuthorization(ctx, identity.subject, args.clientId);
    },
});
```
**配置选项( OAuthConfig)**

```typescript
interface OAuthConfig {
    // REQUIRED: RSA private key in PEM format
    privateKey: string;

    // REQUIRED: JWKS for token verification (public keys only)
    jwks: string;

    // REQUIRED: Your application URL
    siteUrl: string;

    // OPTIONAL: Convex deployment URL (if different from siteUrl)
    convexSiteUrl?: string;

    // OPTIONAL: OAuth endpoint prefix (default: "/oauth")
    // Normalized to a leading slash, trailing slash removed; "/" means root.
    // Must match the route prefix you register in http.ts.
    prefix?: string;

    // OPTIONAL: Comma-separated list of allowed CORS origins
    allowedOrigins?: string;

    // OPTIONAL: Allowed scopes for dynamic client registration
    allowedScopes?: string[];

    // OPTIONAL: JWT audience claim (default: "convex")
    // Set to "oauth-provider" when using Better Auth to distinguish tokens
    applicationID?: string;

    // REQUIRED: Function to get authenticated user ID
    // Must return a Convex users table Id (string)
    // Returns null if user is not authenticated
    getUserId?: (ctx: ActionCtx, request: Request) => Promise<string | null> | string | null;

    // OPTIONAL: Enable dynamic client registration (default: false)
    allowDynamicClientRegistration?: boolean;
}
```

## 调试验证

### 撤销和访问托肯寿命

访问符是JWT,可以无国籍地与JWKS验证. 仅凭无国籍核查,无法观察授权撤销、授权码重放检测或刷新式家庭撤销,直到访问令牌到期.

Convex 资源服务器, 有线 `createAuthorizationChecker()` 输入 `createAuthHelper()` 因此不记名请求检查当前授权记录:

```typescript
import { createAuthHelper, OAuthProvider } from "@codefox-inc/oauth-provider";
import { components } from "./_generated/api";

const oauthProvider = new OAuthProvider(components.oauthProvider, {
    privateKey: process.env.JWT_PRIVATE_KEY!,
    jwks: process.env.JWKS!,
    siteUrl: process.env.SITE_URL!,
});

export const authHelper = createAuthHelper({
    providers: ["anonymous"],
    checkAuthorization: oauthProvider.createAuthorizationChecker(),
});
```

如果您在 Convex 外验证访问符 `verifyAccessToken()` 只有被吊销的访问令牌有效,直到其 `exp` 时间。 如果需要立即吊销,则使用简短的访问存储寿命并使用资源服务器授权检查.

更新此组件后, 重新运行 Convex 代码生成( 例如) `convex dev --once` 或您的仓库的代码脚本。 生成的组件参考和计划必须与软件包版本相匹配; `prompt=none` 静态授权还依赖于最近生成的 `getAuthorization` 查询参考.

### 在凸起函数中

```typescript
import { query } from "./_generated/server";

export const protectedQuery = query({
    handler: async (ctx) => {
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) throw new Error("Not authenticated");

        // Token is already verified by Convex Auth
        // Use identity.subject for user ID
        return { userId: identity.subject };
    },
});
```

**外部调试验证**

```typescript
import { verifyAccessToken } from "@codefox-inc/oauth-provider";

const payload = await verifyAccessToken(
    token,
    {
        jwks: process.env.JWKS!,
        siteUrl: process.env.SITE_URL!,
        // If using Better Auth, specify the applicationID
        // applicationID: "oauth-provider",
    },
    issuerUrl
);

console.log("User ID:", payload.sub);
console.log("Scopes:", payload.scp);
console.log("Client ID:", payload.cid);
```
**区分 OAuth 从会话托肯**

当使用多个认证系统(例如:Better Auth + OAuth Profer)时,您可以通过检查发件人来区分符:

```typescript
import { isOAuthToken, getOAuthClientId } from "@codefox-inc/oauth-provider";

// Option 1: Using helper functions
const identity = await ctx.auth.getUserIdentity();
if (isOAuthToken(identity)) {
    const clientId = getOAuthClientId(identity);
    // Handle OAuth token (MCP clients, third-party apps)
} else {
    // Handle session token (first-party users)
}

// Option 2: Check issuer directly
if (identity?.issuer?.includes("/oauth")) {
    // This is an OAuth token
}
```

## 测试

```bash
bun run test
```

## 许可证

阿帕奇 - 2.0

## 常见问题

### @codefox-inc/oauth-provider 是什么？

OAuth 2\.1 / Convex Auth \(Beta\) 的 OpenID 连接提供者\.

### 如何安装 @codefox-inc/oauth-provider？

在项目目录运行 npm install @codefox-inc/oauth-provider，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：convex（^1\.31\.6）、react（^18\.2\.0 \|\| ^19\.0\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @codefox-inc/oauth-provider 支持 TypeScript 吗？

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

### @codefox-inc/oauth-provider 支持 ESM 还是 CommonJS？

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

### @codefox-inc/oauth-provider 可以商用吗？

@codefox-inc/oauth-provider 使用 Apache-2\.0 许可。宽松许可：允许商业使用、修改和再分发，并附带明确的专利授权；分发时需保留许可与 NOTICE 文件，修改过的文件要注明改动。以上为概括，不构成法律意见，请以许可原文为准。

### @codefox-inc/oauth-provider 的下载量有多少？

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

### @codefox-inc/oauth-provider 还在维护吗？

最新版本 0\.4\.2 发布于 2026/05/17（5 个月前）；至今共发布 11 个版本，其中过去 12 个月发布了 11 个；首次发布于 2026/01/23。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

