A Deno-compatible AT Protocol OAuth client that serves as a drop-in replacement for @atproto/oauth-client-node

Changelog#

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[4.0.2] - 2025-11-27#

Fixed#

  • Token refresh race condition in serverless environments: When concurrent requests trigger token refresh simultaneously across different isolates (e.g., Val Town, Deno Deploy), the second request would fail with "Refresh token replayed" error. Now gracefully handles this by re-reading the session from storage after detecting the replay error.

Added#

  • errorDescription field on TokenExchangeError: OAuth error_description is now exposed as a separate field for better error handling and logging
  • OAuth error response parsing: Token exchange errors now properly parse JSON error responses from OAuth servers, extracting error and error_description fields

Improved#

  • Better error classification for token refresh failures
  • More informative error messages when OAuth operations fail

[4.0.1] - 2025-01-15#

Fixed#

  • Use exact versions in import map instead of semver ranges for better reproducibility
    • @panva/jose: ^6.1.06.1.0
    • @std/assert: 11.0.13

[4.0.0] - 2025-01-15#

Breaking Changes#

  • restore() now throws errors instead of returning null
    • Use try/catch to handle SessionNotFoundError, RefreshTokenExpiredError, NetworkError
    • More explicit error handling with typed error classes
    • See migration guide below for update instructions

Added#

  • Logging System: Configurable logging abstraction
    • Logger interface for custom logging implementations
    • NoOpLogger (default, silent)
    • ConsoleLogger for development/debugging
    • Inject via OAuthClientConfig.logger
  • New Modules: Better code organization
    • src/pkce.ts: PKCE utilities (code verifier, challenge, base64url)
    • src/token-exchange.ts: Token exchange and refresh operations
    • src/logger.ts: Logging abstractions
  • Concurrency Protection: Dual locking system
    • restoreLocks for session restoration (prevents duplicate restore operations)
    • refreshLocks for token refresh (prevents duplicate token requests)
    • Concurrent calls wait on single operation and share results

Improved#

  • Type Safety: Removed all type assertions
    • Added runtime validation in dpop.ts for JWK imports
    • Added type guards in storage.ts for SQLite results
    • Proper type narrowing throughout codebase
  • Code Deduplication: Shared DPoP retry logic
    • Single fetchWithDPoPRetry utility handles nonce challenges
    • Eliminates duplicate code in token exchange and refresh
  • File Organization: All files now under 700 lines
    • client.ts: 731 → 683 lines
    • Better separation of concerns across modules

Removed#

  • Unused API Parameters:
    • Removed signal?: AbortSignal from AuthorizeOptions (not implemented)
    • Removed CallbackOptions interface and parameter (unused)
  • Console Logging: All console.* calls replaced with Logger interface

Migration Guide#

Update restore() error handling:

// Before (v3.x):
const session = await client.restore("session-id");
if (!session) {
  console.log("Session not found");
}

// After (v4.x):
try {
  const session = await client.restore("session-id");
  console.log("Welcome back,", session.handle);
} catch (error) {
  if (error instanceof SessionNotFoundError) {
    console.log("Please log in again");
  } else if (error instanceof RefreshTokenExpiredError) {
    console.log("Session expired, please re-authenticate");
  } else {
    throw error;
  }
}

Add logging (optional):

import { ConsoleLogger } from "@tijs/oauth-client-deno";

const client = new OAuthClient({
  // ... other config
  logger: new ConsoleLogger(), // Enable debug logging
});

[3.0.0] - 2025-01-11#

Changed#

  • BREAKING: restore() method now throws typed errors instead of returning null on failure
    • Throws SessionNotFoundError when session doesn't exist in storage
    • Throws RefreshTokenExpiredError when refresh token has expired
    • Throws RefreshTokenRevokedError when refresh token has been revoked
    • Throws NetworkError for transient network failures
    • Throws TokenExchangeError for other token refresh failures
    • Throws SessionError for unexpected session restoration failures

Added#

  • New Error Types: Added specific error classes for better error handling and debugging
    • SessionNotFoundError: Session not found in storage
    • RefreshTokenExpiredError: Refresh token has expired
    • RefreshTokenRevokedError: Refresh token has been revoked
    • NetworkError: Network-related failures (retryable)
  • Detailed Error Logging: Added comprehensive logging throughout session restoration and token refresh flows
    • Logs session lookup attempts
    • Logs token refresh operations
    • Logs all error conditions with context

Improved#

  • Error Visibility: Session restoration failures now provide detailed error information instead of silent null returns
  • Error Classification: Automatic classification of token exchange errors into specific error types
  • Debugging: Enhanced logging makes it easier to diagnose OAuth session issues in production

Migration Guide#

Applications using restore() must now handle errors instead of checking for null:

Before (v2.x):

const session = await client.restore("session-id");
if (!session) {
  // Handle failure - but why did it fail?
  console.log("Session not found");
}

After (v3.x):

try {
  const session = await client.restore("session-id");
  // Use session
} catch (error) {
  if (error instanceof SessionNotFoundError) {
    // User needs to log in again
  } else if (error instanceof RefreshTokenExpiredError) {
    // Refresh token expired - re-authenticate required
  } else if (error instanceof NetworkError) {
    // Temporary network issue - retry may help
  }
}

[2.1.0] - 2025-01-17#

Added#

  • Concurrency-Safe Session Restore: Added per-session lock manager to prevent race conditions when multiple concurrent requests try to restore the same session
    • Prevents duplicate token refresh requests when session expires
    • Concurrent requests for the same session now wait for and share the result of the first refresh operation
    • Locks are per-DID, so different users' sessions are not affected by each other
    • Automatic lock cleanup when restore operation completes
    • Zero breaking changes - completely internal implementation detail

Fixed#

  • Race Condition in Token Refresh: Fixed issue where concurrent API requests during session expiry could cause "OAuth session not found" errors
    • Multiple endpoints calling restore() simultaneously would all trigger refresh, causing race conditions
    • Now only one refresh happens per session even if 10+ endpoints call restore() concurrently
    • Resolves intermittent 503 errors in multi-endpoint applications

Improved#

  • Developer Experience: Enhanced JSDoc documentation for restore() method to clarify concurrency-safe behavior

[2.0.0] - 2025-09-17#

Changed#

  • BREAKING: Added required pdsUrl property to OAuthSession interface for consistency with hono-oauth-sessions v0.3.0
  • Enhanced type compatibility with updated AT Protocol OAuth ecosystem

[1.0.5] - 2025-09-07#

Fixed#

  • DPoP Private Key Import: Corrected key usage flags for private key import to use only ["sign"] instead of ["sign", "verify"], matching Web Crypto API requirements for ECDSA private keys
  • Fixed persistent "Invalid key usage" error in DPoP authentication

[1.0.4] - 2025-09-07#

Fixed#

  • DPoP Key Import Issue: Fixed "Invalid key usage" error by cleaning JWK before import to remove conflicting key_ops fields that may be added by the jose library's exportJWK function
  • Improved compatibility with Web Crypto API strict key usage validation

[1.0.3] - 2025-09-07#

Fixed#

  • DPoP Key Generation/Import Alignment: Fixed key usage flags mismatch between generation and import operations
  • Updated import function to use ["sign", "verify"] to match generation flags
  • Added complete session data storage support for hono-oauth-sessions integration

[1.0.2] - 2025-09-07#

Fixed#

  • Interface Compatibility: Added toJSON() method to OAuthSession interface for hono-oauth-sessions compatibility
  • Enhanced session serialization support for complete OAuth data persistence

[1.0.1] - 2025-09-05#

Added#

  • Comprehensive JSDoc Documentation: Added detailed JSDoc comments to all public symbols including:
    • Complete error class documentation with examples and use cases
    • Enhanced resolver class documentation with usage patterns
    • Detailed function documentation for all exported utilities
    • Improved client method documentation with comprehensive examples
  • JSR Documentation Compliance: Updated all documentation to follow JSR best practices for symbol documentation

Improved#

  • Developer Experience: All public APIs now have rich documentation with examples
  • IDE Support: Enhanced IntelliSense and auto-completion with detailed parameter descriptions
  • Error Handling: Clear documentation for all error types and when they are thrown

1.0.0 - 2025-08-31#

Changed#

  • BREAKING: API Interface Updates: Updated method signatures to better align with @atproto/oauth-client patterns
    • authorize() now returns URL object instead of string
    • callback() now accepts URLSearchParams instead of object with string properties
    • callback() now returns { session: OAuthSession; state: string | null } format
    • Session now implements OAuthSession interface with sub and aud properties
    • Added AuthorizeOptions and CallbackOptions interfaces matching @atproto patterns

Added#

  • Documentation Improvements: Consolidated README with clear "Opinionated Design" section
    • Clarified that this is NOT a drop-in replacement for @atproto/oauth-client-node
    • Emphasized handle-focused design and Deno-first approach
    • Simplified comparison table and removed repetitive messaging
  • Bun Compatibility: Confirmed Web Crypto API compatibility with Bun runtime
    • All core functionality works with Bun when using npm dependencies instead of JSR

Technical Notes#

  • While interface signatures now align better with @atproto patterns, this client remains handle-focused
  • Accepts AT Protocol handles only (not DIDs or URLs like @atproto/oauth-client-node)
  • Built for Deno environments with Web Crypto API, not Node.js

0.1.2 - 2025-08-31#

Fixed#

  • DPoP Nonce Handling: Fixed HTTP status code handling for DPoP nonce errors to comply with RFC 9449
    • Authorization server DPoP nonce errors now correctly check for HTTP 400 status (was incorrectly checking 401)
    • Resource server DPoP nonce errors continue to use HTTP 401 status as per specification
    • This resolves "use_dpop_nonce" errors when authenticating with AT Protocol servers
  • OAuth Discovery: Fixed authentication server discovery endpoint path
    • Changed from incorrect /.well-known/atproto-did to correct /.well-known/oauth-protected-resource
    • Ensures proper OAuth server endpoint discovery for custom domains and personal PDS instances

Changed#

  • Updated token exchange logic to be more robust with proper error status code detection
  • Improved error messages for DPoP nonce-related authentication failures

0.1.1 - 2025-08-31#

Added#

  • Version bump for JSR compatibility testing

0.1.0 - 2025-08-31#

Added#

  • Initial release of AT Protocol OAuth client for Deno
  • Full OAuth 2.0 + PKCE implementation for AT Protocol
  • DPoP (Demonstrating Proof of Possession) support using Web Crypto API
  • Comprehensive handle resolution with Slingshot integration and fallbacks
  • Session management with automatic token refresh
  • Multiple storage backends:
    • MemoryStorage for development and testing
    • LocalStorage for browser environments
    • Extensible storage interface for custom implementations
  • Complete error handling with specific error types:
    • OAuthError, InvalidHandleError, HandleResolutionError
    • PDSDiscoveryError, TokenExchangeError, DPoPError
    • SessionError, InvalidStateError, AuthorizationError
  • Cross-platform compatibility (Deno, browsers, Web Standards environments)
  • Comprehensive test suite (25 tests, 74 test steps)
  • Full TypeScript support with strict type checking
  • API compatibility with @atproto/oauth-client-node

Key Features#

  • Handle Resolution: Slingshot-first resolution with automatic fallbacks to Bluesky API and direct handle lookup
  • Storage Flexibility: Built-in storage implementations with TTL support
  • Security: Full DPoP implementation using ECDSA P-256 keys and Web Crypto API
  • Error Handling: Comprehensive error types with proper error chaining
  • Session Management: Automatic token refresh and session persistence
  • Mobile Support: Custom URL scheme support for mobile app integration

Technical Implementation#

  • Built specifically for Deno runtime using Web Crypto API
  • Zero Node.js dependencies - uses jsr:@panva/jose for JWT operations
  • Implements AT Protocol OAuth specification with full DPoP support
  • Uses Web Standards for maximum cross-platform compatibility