# @atproto/oauth-client 中文文档

规范地址：https://npmcn.com/package/%40atproto/oauth-client/

版本：0\.9\.3
npm 元数据更新时间：2026-11-06T01:43:28.599Z
中文译文更新时间：2026-11-07T03:51:06.016Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40atproto/oauth-client
原文对照：https://npmcn.com/package/%40atproto/oauth-client/?lang=original
项目主页：https://atproto.com/
源码仓库：https://github.com/bluesky-social/atproto

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

- 包体积（解压后）：345 kB，96 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22
- 运行依赖：14 个
- 首次发布：2024-06-18T20:02:15.498Z
- 最新版本发布：2026-11-06T01:43:28.238Z
- 近 30 天下载量：548321（2026-10-07 至 2026-11-05，npm 官方下载统计）
- 已发布版本数：97（近 12 个月 47 个）

## 中文简介

ATPROTO PDS的OAuth客户端. 本套件作为特定环境执行的共同基础(NodeJS,浏览器,React-Native).

## 安装

```sh
npm install @atproto/oauth-client
```

也可以使用：`pnpm add @atproto/oauth-client`、`yarn add @atproto/oauth-client`、`bun add @atproto/oauth-client`

## README

# @atproto/oauth-客户端: atproto flavoresed OAuth客户端

用于执行的核心库 [原子弹](https://atproto.com/ "AT Protocol") OAuth客户端.

关于浏览器的特定执行,见 [@atproto/oauth-客户端浏览器](https://www.npmjs.com/package/@atproto/oauth-client-browser)。 。 。 . 关于节点的具体执行,见 [@atproto/oauth-客户端-节点](https://www.npmjs.com/package/@atproto/oauth-client-node)。 。 。 .

## 使用量

### 配置

```ts
import { OAuthClient, Key, Session } from '@atproto/oauth-client'
import { JoseKey } from '@atproto/jwk-jose' // NodeJS/Browser only

const client = new OAuthClient({
  handleResolver: 'https://my-backend.example', // backend instances should use a DNS based resolver
  responseMode: 'query', // or "fragment" (frontend only) or "form_post" (backend only)

  // These must be the same metadata as the one exposed on the
  // "client_id" endpoint (except when using a loopback client)
  clientMetadata: {
    client_id: 'https://my-app.example/atproto-oauth-client.json',
    jwks_uri: 'https://my-app.example/jwks.json',
  },

  runtimeImplementation: {
    // A runtime specific implementation of the crypto operations needed by the
    // OAuth client. See "@atproto/oauth-client-browser" for a browser specific
    // implementation. The following example is suitable for use in NodeJS.

    createKey(algs: string[]): Promise<Key> {
      // algs is an ordered array of preferred algorithms (e.g. ['RS256', 'ES256'])

      // Note, in browser environments, it is better to use non extractable keys
      // to prevent the private key from being stolen. This can be done using
      // the WebcryptoKey class from the "@atproto/jwk-webcrypto" package. The
      // inconvenient of these keys (which is also what makes them stronger) is
      // that the only way to persist them across browser reloads is to save
      // them in the indexed DB.
      return JoseKey.generate(algs)
    },

    getRandomValues(length: number): Uint8Array | PromiseLike<Uint8Array> {
      return crypto.getRandomValues(new Uint8Array(length))
    },

    digest(
      bytes: Uint8Array,
      algorithm: { name: string },
    ): Uint8Array | PromiseLike<Uint8Array> {
      // sha256 is required. Unsupported algorithms should throw an error.

      if (algorithm.name.startsWith('sha')) {
        const subtleAlgo = `SHA-${algorithm.name.slice(3)}`
        const buffer = await crypto.subtle.digest(subtleAlgo, bytes)
        return new Uint8Array(buffer)
      }

      throw new TypeError(`Unsupported algorithm: ${algorithm.name}`)
    },

    requestLock: <T>(
      name: string,
      fn: () => T | PromiseLike<T>,
    ): Promise<T> => {
      // This function is used to prevent concurrent refreshes of the same
      // credentials. It is important to ensure that only one refresh is done at
      // a time to prevent the sessions from being revoked.

      // The following example shows a simple in-memory lock. In a real
      // application, you should use a more robust solution (e.g. a system wide
      // lock manager). Note that not providing a lock will result in an
      // in-memory lock to be used (DO NOT copy-paste the following code).

      declare const locks: Map<string, Promise<void>>

      const current = locks.get(name) || Promise.resolve()
      const next = current
        .then(fn)
        .catch(() => {})
        .finally(() => {
          if (locks.get(name) === next) locks.delete(name)
        })

      locks.set(name, next)
      return next
    },
  },

  stateStore: {
    // A store for saving state data while the user is being redirected to the
    // authorization server.

    set(key: string, internalState: InternalStateData): Promise<void> {
      throw new Error('Not implemented')
    },
    get(key: string): Promise<InternalStateData | undefined> {
      throw new Error('Not implemented')
    },
    del(key: string): Promise<void> {
      throw new Error('Not implemented')
    },
  },

  sessionStore: {
    // A store for saving session data.

    set(sub: string, session: Session): Promise<void> {
      throw new Error('Not implemented')
    },
    get(sub: string): Promise<Session | undefined> {
      throw new Error('Not implemented')
    },
    del(sub: string): Promise<void> {
      throw new Error('Not implemented')
    },
  },

  keyset: [
    // For backend clients only, a list of private keys to use for signing
    // credentials. These keys MUST correspond to the public keys exposed on the
    // "jwks_uri" of the client metadata. Note that the jwks JSON corresponding
    // to the following keys can be obtained using the `client.jwks` getter.
    await JoseKey.fromImportable(process.env.PRIVATE_KEY_1),
    await JoseKey.fromImportable(process.env.PRIVATE_KEY_2),
    await JoseKey.fromImportable(process.env.PRIVATE_KEY_3),
  ],
})
```

### 认证

```ts
const url = await client.authorize('foo.bsky.team', {
  state: '434321',
  prompt: 'consent',
  scope: 'email',
  ui_locales: 'fr',
})
```

访问用户 `url`。然后,一旦它被重定向到调回URI,就执行以下操作:

```ts
// Parse the query params from the callback URI
const params = new URLSearchParams('code=...&state=...')

// Process the callback using the OAuth client
const result = await client.callback(params)

// Verify the state (e.g. to link to an internal user)
result.state === '434321' // true

const oauthSession = result.session
```

登录过程导致 `OAuthSession` 可以用来制造 向资源服务器认证请求 。 此实例会自动 需要时刷新证书.

### 提出经认证的请求

这个 `OAuthSession` 签名后获得的实例可用于制作 向用户的PDS验证请求. 有两个主要的使用案例:

1.  向Bluesky的AppView提出认证请求,以便获取 操作来自 `app.bsky` 词典.
    
2.  向您的 AppView 提出认证请求,以便获取 操作您自己的词典中的数据.
    

#### 向 Bluesky 的 AppView 提出认证请求

这个 `@atproto/oauth-client` 软件包提供 `OAuthSession` 类可以是 曾向Bluesky的AppView提出认证请求. 这可以做到 建造一个 `Agent` (从) `@atproto/api`使用 `OAuthSession` 举个例子.

```ts
import { Agent } from '@atproto/api'

const agent = new Agent(oauthSession)

// Make an authenticated request to the server. New credentials will be
// automatically fetched if needed (causing sessionStore.set() to be called).
await agent.post({
  text: 'Hello, world!',
})

// revoke credentials on the server (causing sessionStore.del() to be called)
await agent.signOut()
```

#### 向您的 AppView 提出认证请求

这个 `OAuthSession` 签名后获得的实例可用于即时处理 页:1 `XrpcClient` 类从 `@atproto/xrpc` 软件包.

```ts
import { Lexicons } from '@atproto/lexicon'
import { OAuthClient } from '@atproto/oauth-client' // or "@atproto/oauth-client-browser" or "@atproto/oauth-client-node"
import { XrpcClient } from '@atproto/xrpc'

// Define your lexicons
const myLexicon = new Lexicons([
  {
    lexicon: 1,
    id: 'com.example.query',
    defs: {
      main: {
        // ...
      },
    },
  },
])

// Describe your app's oauth client
const oauthClient = new OAuthClient({
  // ...
})

// Authenticate the user
const oauthSession = await oauthClient.restore('did:plc:123')

// Instantiate a client using the `oauthSession` as fetch handler object
const client = new XrpcClient(oauthSession, myLexicon)

// Make authenticated calls
const response = await client.call('com.example.query')
```

请注意, 用户的PDS 可能不知道您的词汇表, 也不知道该怎么处理 这些呼吁(PDS)只授权执行 `com.atproto` 词典. 单位 您需要有一个后端来处理这些 打电话 然后您可以指示您的 PDS 将这些电话转发到您的后端 .

```ts
const response = await client.call(
  'com.example.query',
  {
    // Params
  },
  {
    headers: {
      // The PDS will proxy calls to the specified service in did:plc:xyz's did document.
      // These calls will be authenticated using "service auth", a single use JWT Bearer token, signed with the logged-in user's private key.
      'atproto-proxy': 'did:plc:xyz#serviceId',
    },
  },
)
```

你也可以即兴演奏 `XrpcClient` 有自定义的类 `fetch` 函数 将提供 `atproto-proxy` 所有通话的页眉 :

```ts
const boundClient = new XrpcClient((url, init) => {
  const headers = new Headers(init?.headers)

  // Add the atproto-proxy header if it is not already present
  if (!headers.has('atproto-proxy')) {
    headers.set('atproto-proxy', 'did:plc:xyz#serviceId')
  }

  return oauthSession.fetchHandler(url, { ...init, headers })
}, myLexicon)

// No need to specify the atproto-proxy header anymore
const response = await boundClient.call('com.example.query')
```

> \[注\]
> 
> 通过 PDS 代理每通呼叫不推荐执行 因为它会增加只读到您的词汇的空闲性. 这样做也会妨碍您的后端预想写作 在网络上。 实际上,信访会被发送到PDS,然后PDS 通过接力(a.k.a."firehose")在网络上传播. 这个会 引入写作时间和写作时间之间的延迟 由您的后端处理 .
> 
> 为了避免这些问题,建议你执行你的建议 使用后端换前端模式的后端. 这个后端负责 并能够预测 在网络上写.
> 
> 更多信息,请访问 [原子弹](https://atproto.com/ "AT Protocol") 文献网站.

## 预付款使用情况

### 听会话更新并删除

这个 `OAuthClient` 当会话更新或删除时,会发出事件.

```ts
import {
  Session,
  TokenRefreshError,
  TokenRevokedError,
} from '@atproto/oauth-client'

client.addEventListener('updated', (event: CustomEvent<Session>) => {
  console.log('Refreshed tokens were saved in the store:', event.detail)
})

client.addEventListener(
  'deleted',
  (
    event: CustomEvent<{
      sub: string
      cause: TokenRefreshError | TokenRevokedError | unknown
    }>,
  ) => {
    console.log('Session was deleted from the session store:', event.detail)

    const { cause } = event.detail

    if (cause instanceof TokenRefreshError) {
      // - refresh_token unavailable or expired
      // - oauth response error (`cause.cause instanceof OAuthResponseError`)
      // - session data does not match expected values returned by the OAuth server
    } else if (cause instanceof TokenRevokedError) {
      // Session was revoked through:
      // - agent.signOut()
      // - client.revoke(sub)
    } else {
      // An unexpected error occurred, causing the session to be deleted
    }
  },
)
```

### 强制用户重新认证

```ts
const url = await client.authorize(handle, {
  prompt: 'login',
  state,
})
```

或者说

```ts
const url = await client.authorize(handle, {
  state,
})
```

### 安静的标志... 单位

使用无声签名需要处理回调端点上的回调.

```ts
async function createLoginUrl(handle: string, state?: string): string {
  return client.authorize(handle, {
    state,
    // Use "prompt=none" to attempt silent sign-in
    prompt: 'none',
  })
}

async function handleCallback(params: URLSearchParams) {
  try {
    return await client.callback(params)
  } catch (err) {
    // Silent sign-in failed, retry without prompt=none
    if (
      err instanceof OAuthCallbackError &&
      ['login_required', 'consent_required'].includes(err.params.get('error'))
    ) {
      // Do *not* use prompt=none when retrying (to avoid infinite redirects)
      const url = await client.authorize(handle, { state: err.state })

      // Allow calling code to catch the error and redirect the user to the new URL
      return new MyLoginRequiredError(url)
    }

    throw err
  }
}
```

## 常见问题

### @atproto/oauth-client 是什么？

ATPROTO PDS的OAuth客户端\. 本套件作为特定环境执行的共同基础\(NodeJS,浏览器,React-Native\)\.

### 如何安装 @atproto/oauth-client？

在项目目录运行 npm install @atproto/oauth-client，或使用 pnpm add / yarn add / bun add。

### @atproto/oauth-client 支持 TypeScript 吗？

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

### @atproto/oauth-client 支持 ESM 还是 CommonJS？

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

### @atproto/oauth-client 需要什么版本的 Node\.js？

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

### @atproto/oauth-client 可以商用吗？

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

### @atproto/oauth-client 的下载量有多少？

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

### @atproto/oauth-client 还在维护吗？

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

