Skip to main content
Rowboat implements OAuth 2.0 with PKCE (Proof Key for Code Exchange) for secure authentication with third-party services like Google and Fireflies.ai.

Architecture Overview

OAuth Flow with PKCE

PKCE (RFC 7636) eliminates the need for client secrets in native/desktop apps. Rowboat uses S256 code challenge method for maximum security.

Authorization Flow Diagram

Core Components

1. Provider Configuration

Each OAuth provider has a configuration defining discovery and client registration:
Location: core/src/auth/providers.tsSchema:
Google Configuration:
Fireflies Configuration (DCR):

2. OAuth Client (oauth-client.ts)

Discovery Process:
  1. OIDC Discovery (issuer mode):
    • Fetch {issuer}/.well-known/openid-configuration
    • Extract authorization_endpoint, token_endpoint, etc.
    • Cache configuration per issuer:clientId
  2. Static Configuration:
    • Use pre-configured endpoints
    • No discovery request needed
Implementation:
Purpose: Generate cryptographically secure code verifier and challengeImplementation:
RFC 7636 Spec:
  • Verifier: 43-128 characters, base64url-encoded
  • Challenge: SHA-256 hash of verifier, base64url-encoded
  • Method: S256 (SHA-256)
Process:
  1. Generate PKCE verifier + challenge
  2. Generate random state (CSRF protection)
  3. Build authorization URL with parameters
  4. Open URL in system browser
Implementation:
Example URL:
Process:
  1. Capture authorization code from redirect
  2. Validate state matches (CSRF protection)
  3. Exchange code + verifier for tokens
  4. Parse and return tokens
Implementation:
Token Response:
Process:
  1. Check if access token is expired
  2. Use refresh token to get new access token
  3. Preserve existing scopes/refresh token if not returned
Implementation:

3. Dynamic Client Registration (DCR)

DCR (RFC 7591) allows apps to register OAuth clients programmatically, eliminating the need for pre-registered client IDs.
When to use DCR:
  • Provider supports RFC 7591
  • No pre-registered client ID available
  • Want to avoid hardcoding client credentials
Implementation:
Registration Response:

4. Token Storage (repo.ts)

Purpose: Securely persist OAuth tokens per providerStorage Location: ~/.rowboat/auth/{provider}.jsonSchema:
Implementation:

Complete OAuth Flow Example

Google OAuth Flow

Security Considerations

NEVER store client secrets in desktop apps. Use PKCE instead, which eliminates the need for secrets.
  • No client secret: Cannot be extracted from app bundle
  • One-time verifier: Different verifier for each authorization
  • Challenge verification: Server validates verifier matches challenge
  • MITM protection: Attacker cannot use intercepted code without verifier
Purpose: Prevent cross-site request forgery attacksHow it works:
  1. Generate random state before authorization
  2. Include state in authorization URL
  3. Provider returns state in callback
  4. Validate returned state matches original
Implementation:
Best practices:
  • Use http://localhost:{port}/callback for desktop apps
  • Choose random available port (8080-8090)
  • Close local server after receiving callback
  • Validate redirect URI matches registered URI
Implementation:
Current implementation:
  • Tokens stored in ~/.rowboat/auth/ as JSON files
  • File permissions: 600 (owner read/write only)
  • No encryption (relies on OS file permissions)
Future improvements:
  • Use OS keychain (macOS Keychain, Windows Credential Manager)
  • Encrypt tokens at rest
  • Use secure enclave for token encryption keys

Error Handling

Error Types:
  • invalid_grant - Code expired, already used, or invalid
  • invalid_client - Client ID not recognized
  • invalid_request - Missing required parameters
  • access_denied - User declined authorization
  • server_error - Provider had an internal error

Testing OAuth Flows

Code References

  • OAuth Client: core/src/auth/oauth-client.ts:1
  • Provider Configs: core/src/auth/providers.ts:53
  • Token Repository: core/src/auth/repo.ts
  • Token Types: core/src/auth/types.ts
  • Client ID Provider: core/src/auth/provider-client-id.ts