@atproto/lex-password-session

中文文档
0.3.2发布于 2026/11/06MITTypeScriptES Module构建来源已验证近 30 天下载 4.3万

Password based client authentication for AT Lexicons

原始使用文档
文档目录17 节
  1. Quick Start
  2. PasswordSession
  3. Login
  4. Two-Factor Authentication
  5. Resume Session
  6. Logout
  7. Static Delete
  8. Create Account
  9. Session Hooks
  10. onUpdated
  11. onUpdateFailure
  12. onDeleted
  13. onDeleteFailure
  14. Session Data
  15. Error Handling
  16. Using with Client
  17. License

@atproto/lex-password-session

Password-based session authentication for AT Protocol Lexicons. See the Changelog for version history.

npm install @atproto/lex-password-session
  • Session management with automatic token refresh
  • Hooks for persisting and monitoring session state
  • PDS endpoint discovery from DID documents
  • Two-factor authentication support

[!IMPORTANT]

This package is currently in preview. The API and features are subject to change before the stable release.

What is this?

@atproto/lex-password-session provides a PasswordSession class that implements the Agent interface from @atproto/lex-client. It handles password-based authentication with AT Protocol services, including:

  1. Creating sessions with username/password credentials
  2. Automatic token refresh when access tokens expire
  3. Session persistence through lifecycle hooks
  4. Graceful logout with server-side session cleanup
import { Client } from '@atproto/lex-client'
import { PasswordSession } from '@atproto/lex-password-session'
import * as app from './lexicons/app.js'

// Login with credentials
const session = await PasswordSession.login({
  service: 'https://bsky.social',
  identifier: 'alice.bsky.social',
  password: 'app-password',
  onUpdated: (data) => saveToStorage(data),
  onDeleted: (data) => clearStorage(data.did),
})

const client = new Client(session)

// Make authenticated requests
const profile = await client.call(app.bsky.actor.getProfile, {
  actor: session.did,
})

Quick Start

1. Install the package

npm install @atproto/lex-password-session @atproto/lex-client

2. Login and make requests

import { Client } from '@atproto/lex-client'
import { PasswordSession } from '@atproto/lex-password-session'

const session = await PasswordSession.login({
  service: 'https://bsky.social',
  identifier: 'your-handle.bsky.social',
  password: 'your-app-password',
})

const client = new Client(session)

// Make authenticated API calls
console.log('Logged in as:', session.did)

PasswordSession

The PasswordSession class manages password-based authentication sessions.

Login

Create a new session with username and password:

import { PasswordSession } from '@atproto/lex-password-session'

const session = await PasswordSession.login({
  service: 'https://bsky.social',
  identifier: 'alice.bsky.social', // handle or email
  password: 'app-password',
  onUpdated: (data) => {
    // Persist session for later restoration
    localStorage.setItem('session', JSON.stringify(data))
  },
  onDeleted: () => {
    localStorage.removeItem('session')
  },
})

console.log('Logged in as:', session.did)

The login() method throws on failure. For expected errors like invalid credentials, an XrpcResponseError is thrown. For 2FA requirements, a LexAuthFactorError is thrown.

Two-Factor Authentication

[!CAUTION]

Two-factor authentication only applies when using main account credentials, which is strongly discouraged. Password authentication should be used with app passwords only because they are designed for programmatic access (bots, scripts, CLI tools). For user-facing applications, use OAuth via @atproto/oauth-client which provides better security and user control.

If the account has 2FA enabled, login will throw a LexAuthFactorError:

import {
  PasswordSession,
  LexAuthFactorError,
} from '@atproto/lex-password-session'

async function loginWith2FA(
  identifier: string,
  password: string,
  authFactorToken?: string,
): Promise<PasswordSession> {
  try {
    return await PasswordSession.login({
      service: 'https://bsky.social',
      identifier,
      password,
      authFactorToken,
      onUpdated: (data) => saveToStorage(data),
      onDeleted: (data) => removeFromStorage(data.did),
    })
  } catch (err) {
    if (err instanceof LexAuthFactorError && !authFactorToken) {
      // 2FA required - prompt user for code
      const token = await promptUserFor2FACode(err.message)
      return loginWith2FA(identifier, password, token)
    }
    throw err
  }
}

Resume Session

Restore a previously saved session:

import { PasswordSession, SessionData } from '@atproto/lex-password-session'

// Load session from storage
const savedSession: SessionData = JSON.parse(localStorage.getItem('session')!)

// Resume the session (automatically refreshes tokens)
const session = await PasswordSession.resume(savedSession, {
  onUpdated: (data) => {
    localStorage.setItem('session', JSON.stringify(data))
  },
  onDeleted: () => {
    localStorage.removeItem('session')
  },
})

console.log('Session resumed for:', session.did)

// Access session properties
console.log(session.did) // User's DID
console.log(session.handle) // User's handle
console.log(session.destroyed) // false (session is active)

[!NOTE]

resume() automatically calls refresh() to ensure the session is valid and tokens are current.

Logout

End the session and notify the server:

await session.logout()

After logout:

  • The onDeleted hook is called
  • The session is marked as destroyed (session.destroyed === true)
  • Further requests will throw 'Logged out'

Static Delete

Delete a session without creating a session instance:

import { PasswordSession, SessionData } from '@atproto/lex-password-session'

const data: SessionData = JSON.parse(localStorage.getItem('session')!)

// Delete the session on the server
await PasswordSession.delete(data)

This is useful for cleanup scenarios where you don't need to make additional requests.

Create Account

Create a new account and get an authenticated session:

import { PasswordSession } from '@atproto/lex-password-session'

const session = await PasswordSession.createAccount(
  {
    handle: 'alice.bsky.social',
    email: '[email protected]',
    password: 'secure-password',
  },
  {
    service: 'https://bsky.social',
    onUpdated: (data) => saveToStorage(data),
    onDeleted: (data) => removeFromStorage(data.did),
  },
)

console.log('Account created:', session.did)

Session Hooks

Hooks provide callbacks for session lifecycle events. All hooks receive the session instance as this context.

onUpdated

Called when the session is successfully created or refreshed:

const session = await PasswordSession.login({
  service: 'https://bsky.social',
  identifier: 'alice.bsky.social',
  password: 'app-password',
  onUpdated(data) {
    // `this` is the PasswordSession instance
    console.log('Session updated for:', this.did)

    // Persist the updated session
    saveSession(data)
  },
})

[!IMPORTANT]

Requests are blocked while onUpdated is running. Keep this callback fast to avoid delays.

onUpdateFailure

Called when token refresh fails due to transient errors (network issues, server unavailability):

{
  onUpdateFailure(data, error) {
    console.warn('Token refresh failed:', error.message)
    // Session may still be valid - consider retry logic
  }
}

onDeleted

Called when the session is terminated (logout or server-side invalidation):

{
  onDeleted(data) {
    console.log('Session ended for:', data.did)
    clearPersistedSession(data.did)
    redirectToLogin()
  }
}

onDeleteFailure

Called when logout fails due to transient errors:

{
  onDeleteFailure(data, error) {
    console.error('Logout failed:', error.message)
    // Consider queuing for retry to avoid orphaned sessions
    queueLogoutRetry(data)
  }
}

[!WARNING]

Ignoring delete failures can leave sessions active on the server. Implement retry logic for security-sensitive applications.

Session Data

The SessionData type contains all data needed to authenticate and restore sessions:

type SessionData = {
  // Session credentials and user info from createSession response
  accessJwt: string
  refreshJwt: string
  did: string
  handle: string
  email?: string
  emailConfirmed?: boolean
  didDoc?: object
  // ... other fields from createSession

  // Original service URL used for login
  service: string
}

Error Handling

The PasswordSession class uses exception-based error handling:

import {
  PasswordSession,
  LexAuthFactorError,
} from '@atproto/lex-password-session'
import { XrpcResponseError } from '@atproto/lex-client'

try {
  const session = await PasswordSession.login({
    service: 'https://bsky.social',
    identifier: 'alice.bsky.social',
    password: 'wrong-password',
  })
} catch (err) {
  if (err instanceof LexAuthFactorError) {
    console.error('2FA required')
  } else if (err instanceof XrpcResponseError) {
    switch (err.error) {
      case 'AuthenticationRequired':
        console.error('Invalid credentials')
        break
      case 'AccountTakedown':
        console.error('Account has been suspended')
        break
      default:
        console.error('Login failed:', err.message)
    }
  } else {
    throw err
  }
}

Common error codes:

Error CodeDescription
AuthenticationRequiredInvalid username or password
AuthFactorTokenRequired2FA code needed
AccountTakedownAccount suspended
ExpiredTokenToken has expired (on refresh)
InvalidTokenToken is invalid

Using with Client

The PasswordSession implements the Agent interface and can be used directly with Client:

import { Client } from '@atproto/lex-client'
import { PasswordSession } from '@atproto/lex-password-session'
import * as app from './lexicons/app.js'

const session = await PasswordSession.login({
  service: 'https://bsky.social',
  identifier: 'alice.bsky.social',
  password: 'app-password',
})

const client = new Client(session)

// The client automatically uses the session for authentication
const profile = await client.call(app.bsky.actor.getProfile, {
  actor: client.assertDid,
})

// Tokens are automatically refreshed when expired
const timeline = await client.call(app.bsky.feed.getTimeline, {
  limit: 50,
})

// Create records
await client.create(app.bsky.feed.post, {
  text: 'Hello from lex-password-session!',
  createdAt: new Date().toISOString(),
})

The session handles:

  • Adding Authorization headers to requests
  • Detecting expired tokens (401 responses or ExpiredToken errors)
  • Automatically refreshing tokens and retrying failed requests
  • Routing requests to the correct PDS based on DID document

License

MIT or Apache2

版本与发布记录

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

版本发布日期距今
0.3.2latest2026/11/061 天前
0.0.0-spaces-alpha-20261105160032alpha2026/11/062 天前
0.0.0-spaces-alpha-202610291719132026/10/309 天前
0.0.0-spaces-alpha-202610221412282026/10/2216 天前
0.3.02026/10/1721 天前
0.0.0-spaces-alpha-202610151435092026/10/1523 天前

其他发布通道:next → 0.1.0-next.0,alpha → 0.0.0-spaces-alpha-20261105160032。

关于 @atproto/lex-password-session 的常见问题

@atproto/lex-password-session 是什么?

Password based client authentication for AT Lexicons

如何安装 @atproto/lex-password-session?

在项目目录运行 npm install @atproto/lex-password-session,或使用 pnpm add / yarn add / bun add。

@atproto/lex-password-session 支持 TypeScript 吗?

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

@atproto/lex-password-session 支持 ESM 还是 CommonJS?

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

@atproto/lex-password-session 需要什么版本的 Node.js?

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

@atproto/lex-password-session 可以商用吗?

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

@atproto/lex-password-session 的下载量有多少?

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

@atproto/lex-password-session 还在维护吗?

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