Skip to content
This repository was archived by the owner on Apr 26, 2026. It is now read-only.

MisterPea/schwab-node-persistent-auth

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

schwab-node-persistent-auth

Superseded: This project has been superseded by schwab-auth-daemon.

New development should happen there. This repository is kept for historical/reference purposes and existing users, but it is no longer the recommended starting point for new setups.

Centralised OAuth token daemon for @misterpea/schwab-node.

The Problem

Schwab allows one active OAuth session per account. If multiple services each try to manage their own auth, they will invalidate each other's tokens.

The Solution

Run one daemon that owns all token work. Every other service reads the current token from the system keychain — no OAuth flow, no conflicts.

┌─────────────────────────────────┐
│  schwab-auth-daemon             │  ← this package
│  • holds SchwabAuth (managed)   │
│  • refreshes token every 20 min │
│  • writes token to keychain     │
└────────────────┬────────────────┘
                 │ keychain
     ┌───────────┴───────────┐
     ▼                       ▼
 Service A              Service B
 createDelegatedAuth()  createDelegatedAuth()
 reads keychain         reads keychain

Setup

1. Install

npm install @misterpea/schwab-node-persistent-auth

2. Configure

Copy .env.example to .env and fill in your Schwab app credentials:

SCHWAB_CLIENT_ID=your_client_id
SCHWAB_CLIENT_SECRET=your_client_secret
SCHWAB_REDIRECT_URI=https://127.0.0.1:8443

# Optional
SCHWAB_REFRESH_INTERVAL_MS=1200000
SCHWAB_KEYCHAIN_SERVICE=schwab-node

Note: Node's --env-file flag includes everything after = as the value, including inline # comments. Put comments on their own lines only.

3. Run the daemon

# Production
node --env-file .env node_modules/.bin/schwab-auth-daemon

# Dev (TypeScript, no build step)
npm run dev

On first run the daemon will open a browser for the initial Schwab OAuth login. After that it runs silently, refreshing the access token on the configured interval and writing it to the system keychain.

Keychain support: macOS Keychain, Windows Credential Manager, Linux libsecret (via keytar).


In Your Other Services

Instead of managing auth yourself, point createDelegatedAuth at the keychain:

import { createDelegatedAuth } from '@misterpea/schwab-node';
import { KeychainTokenStore } from '@misterpea/schwab-node-persistent-auth';

const auth = createDelegatedAuth(new KeychainTokenStore('schwab-node'));

That's it. The service reads the token the daemon keeps fresh — no credentials, no refresh logic, no browser prompts.


Daemon Control (schwab-daemon-ctl)

This package ships a control CLI for managing the daemon when it runs as a launchd service (macOS) or similar system supervisor.

Make the command available globally by linking the package:

cd /path/to/your/schwab-auth-daemon-project
npm link @misterpea/schwab-node-persistent-auth

Or if you cloned this repo directly:

npm link

Then use it from anywhere:

schwab-daemon-ctl <command>
Command Description
start Start the daemon via launchctl
stop Send SIGTERM to the daemon (launchd will restart it if KeepAlive=true)
status Show whether the daemon is running and its PID
auth Clear the stored token and restart the daemon to trigger a fresh OAuth browser flow

Use auth whenever you need to re-authenticate — for example after an extended absence when the refresh token (7-day lifetime) has expired.

# Check if daemon is healthy
schwab-daemon-ctl status

# Force re-authentication (opens browser)
schwab-daemon-ctl auth

The auth command respects the same environment variables as the daemon:

SCHWAB_DAEMON_LABEL=com.my-daemon schwab-daemon-ctl auth

How the Daemon Manages Tokens

Event Action
Startup Calls getAuth() immediately
Every interval Calls getAuth() — refreshes if access token is expired
Refresh token nearing expiry (within 48 h of 7-day limit) Clears token, opens browser for full re-auth
SIGTERM / SIGINT Graceful shutdown

Environment Variables

Variable Default Description
SCHWAB_CLIENT_ID Schwab app client ID (required)
SCHWAB_CLIENT_SECRET Schwab app client secret (required)
SCHWAB_REDIRECT_URI OAuth redirect URI (required)
SCHWAB_REFRESH_INTERVAL_MS 1200000 Token refresh interval in ms. Keep under 30 min (access token lifetime).
SCHWAB_KEYCHAIN_SERVICE schwab-node Keychain namespace. Must match the name passed to new KeychainTokenStore() in every consumer app.
SCHWAB_DAEMON_LABEL com.schwab-auth-daemon launchd service label — used by schwab-daemon-ctl only.

About

Daemon and keychain token store for centralized Schwab OAuth management across multiple @misterpea/schwab-node consumers.

Topics

Resources

License

Stars

Watchers

Forks

Contributors