SchwabApi.Authentication 1.0.0-beta.1

This is a prerelease version of SchwabApi.Authentication.
The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package SchwabApi.Authentication --version 1.0.0-beta.1
                    
NuGet\Install-Package SchwabApi.Authentication -Version 1.0.0-beta.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SchwabApi.Authentication" Version="1.0.0-beta.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SchwabApi.Authentication" Version="1.0.0-beta.1" />
                    
Directory.Packages.props
<PackageReference Include="SchwabApi.Authentication" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SchwabApi.Authentication --version 1.0.0-beta.1
                    
#r "nuget: SchwabApi.Authentication, 1.0.0-beta.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SchwabApi.Authentication@1.0.0-beta.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SchwabApi.Authentication&version=1.0.0-beta.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=SchwabApi.Authentication&version=1.0.0-beta.1&prerelease
                    
Install as a Cake Tool

SchwabApi.Authentication

Cross-platform OAuth 2.0 authentication library for Charles Schwab API.

Installation

dotnet add package SchwabApi.Authentication

Or via NuGet Package Manager:

Install-Package SchwabApi.Authentication

Features

  • Cross-platform: Works on Windows, Linux, and macOS
  • HTTPS localhost: Uses Kestrel + .NET dev certificates for secure OAuth callbacks
  • Headless OAuth flow: Uses system browser + local Kestrel server
  • Automatic token refresh: Background service refreshes tokens before expiration
  • Secure storage: AES-256 encryption with machine-specific keys
  • CSRF protection: State parameter validation
  • Resilience: Retry logic with exponential backoff and circuit breaker
  • Dependency injection: Full Microsoft.Extensions.DependencyInjection support
  • Multi-client support: Optional SQLite storage for multiple accounts

Prerequisites

One-time setup per machine:

dotnet dev-certs https --trust

This generates and trusts a development HTTPS certificate for localhost. Required because Schwab enforces HTTPS on all callback URLs.

  • Windows: Certificate added to Windows Certificate Store (automatic trust)
  • Linux: Certificate added to ~/.dotnet/corefx/cryptography (may need manual trust depending on distro)
  • macOS: Certificate added to macOS Keychain (requires password to trust)

Browser Certificate Warning

Affects all operating systems - This is browser behavior, not OS-specific.

When the OAuth callback redirects to https://127.0.0.1:5001/callback, your browser will show a certificate warning:

  • Why? The .NET dev certificate is issued for "localhost", not the IP "127.0.0.1"
    • Schwab's portal requires 127.0.0.1 in callback URLs (rejects "localhost")
    • Browser sees hostname mismatch: cert says "localhost", URL says "127.0.0.1"
  • Is it safe? Yes - this is your own local development server, not a remote site
  • What to do? Click "Advanced" → "Proceed to 127.0.0.1 (unsafe)" or equivalent
    • Chrome/Edge: Click "Advanced" → "Proceed to 127.0.0.1 (unsafe)"
    • Firefox: Click "Advanced" → "Accept the Risk and Continue"
    • Safari: Click "Show Details" → "visit this website"

The OAuth flow will complete successfully after accepting the warning. The success page shows briefly before auto-closing.

Quick Start

1. Installation

dotnet add package SchwabApi.Authentication

2. Configure Schwab Developer Portal

  1. Go to https://developer.schwab.com
  2. Navigate to your app settings
  3. Set callback URL to: https://127.0.0.1:5001/callback
    • Note: Schwab's portal does not accept localhost, use 127.0.0.1 instead
  4. Save changes

3. Configure Services

using SchwabApi.Authentication.Extensions;

var builder = Host.CreateApplicationBuilder(args);

// Add Schwab OAuth services
builder.Services.AddSchwabOAuth(options =>
{
    options.CallbackPort = 5001; // Kestrel HTTPS port (default)
    options.RefreshBuffer = TimeSpan.FromMinutes(5); // Refresh 5 min before expiration
});

var host = builder.Build();
await host.RunAsync();

4. Manage Credentials Securely

IMPORTANT: Never hardcode credentials! Follow the standard pattern:

For public configuration (callback URLs, base URLs): Create appsettings.json:

{
  "Schwab": {
    "CallbackUrl": "https://127.0.0.1:5001/callback"
  }
}

For confidential credentials (App Key, App Secret): Use environment variables:

# Windows PowerShell
$env:SCHWAB_APP_KEY = "your-app-key"
$env:SCHWAB_APP_SECRET = "your-app-secret"

# Linux/macOS
export SCHWAB_APP_KEY="your-app-key"
export SCHWAB_APP_SECRET="your-app-secret"

Load credentials in code:

using Microsoft.Extensions.Configuration;

// Load configuration from appsettings.json + environment variables
var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: false)
    .AddEnvironmentVariables()
    .Build();

var credentials = new OAuthCredentials
{
    ClientId = Environment.GetEnvironmentVariable("SCHWAB_APP_KEY") 
        ?? throw new InvalidOperationException("SCHWAB_APP_KEY environment variable required"),
    ClientSecret = Environment.GetEnvironmentVariable("SCHWAB_APP_SECRET")
        ?? throw new InvalidOperationException("SCHWAB_APP_SECRET environment variable required"),
    CallbackUrl = configuration["Schwab:CallbackUrl"]
        ?? throw new InvalidOperationException("Schwab:CallbackUrl configuration required")
};

Why this pattern?

  • ✅ Schwab treats both App Key and App Secret as confidential
  • ✅ Credentials stay out of source control
  • ✅ Works in CI/CD environments
  • ✅ Respects security best practices

See the GettingStarted example for a complete implementation.

5. Perform OAuth Authorization

using SchwabApi.Authentication;
using SchwabApi.Authentication.Models;

// Get OAuth service from DI
var oauthService = host.Services.GetRequiredService<IOAuthService>();

// Authorize (opens browser, starts Kestrel server, waits for callback, exchanges code for tokens)
var tokens = await oauthService.AuthorizeAsync(credentials);

Console.WriteLine($"Access token expires: {tokens.ExpiresAt}");
Console.WriteLine($"Refresh token expires: {tokens.RefreshTokenExpiresAt}");

Note: The credentials object should be loaded from environment variables and configuration files (see step 4), not hardcoded.

6. Use Access Token

// Get current access token (auto-refreshes if needed)
var accessToken = await oauthService.GetAccessTokenAsync(credentials.ClientId);

// Use token in API calls
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = 
    new AuthenticationHeaderValue("Bearer", accessToken);

var response = await httpClient.GetAsync("https://api.schwabapi.com/v1/accounts");

7. Force Re-authorization

// Delete tokens and perform fresh authorization
var tokens = await oauthService.ForceReauthorizationAsync(credentials);

Architecture

OAuth Flow

  1. Authorization URL Generation: Creates Schwab OAuth URL with state parameter
  2. Browser Launch: Opens system default browser (cross-platform)
  3. Kestrel Server: Starts temporary Kestrel web server on https://localhost:5001/callback
  4. OAuth Callback: Kestrel captures authorization code from Schwab redirect
  5. Token Exchange: Exchanges authorization code for access/refresh tokens via Schwab API
  6. Secure Storage: Encrypts tokens with AES-256 using machine-specific key
  7. Background Refresh: Automatically refreshes tokens 5 minutes before expiration

Why Kestrel Instead of HttpListener?

Problem: Schwab requires HTTPS for all callback URLs, including localhost. HttpListener requires platform-specific SSL certificate configuration (netsh on Windows, complex setup on Linux/macOS).

Solution: Kestrel (ASP.NET Core's web server) supports HTTPS on localhost using .NET dev certificates, which work cross-platform with a single dotnet dev-certs https --trust command.

Token Lifetimes

  • Access Token: 30 minutes
  • Refresh Token: 7 days
  • Re-authorization Required: Every 7 days

Security

  • Encryption: AES-256 with PBKDF2 key derivation from machine ID
  • CSRF Protection: State parameter validation (32+ bytes, cryptographically random)
  • File Permissions: Tokens file restricted to current user only (chmod 600 on Unix)
  • HTTPS Enforcement: All Schwab API calls use TLS

Resilience

  • Retry Policy: 3 attempts with exponential backoff [2s, 4s, 8s]
  • Circuit Breaker: Opens after 3 failures, 60s cooldown
  • Thread Safety: Semaphore prevents concurrent token refresh

Configuration Options

builder.Services.AddSchwabOAuth(options =>
{
    // OAuth endpoints
    options.AuthorizationEndpoint = "https://api.schwabapi.com/v1/oauth/authorize";
    options.TokenEndpoint = "https://api.schwabapi.com/v1/oauth/token";
    
    // Callback server
    options.CallbackPort = 8080; // Will try 8080-8089
    options.CallbackPath = "/callback";
    
    // Token refresh
    options.RefreshBuffer = TimeSpan.FromMinutes(5);
    options.RefreshCheckInterval = TimeSpan.FromMinutes(1);
    
    // Resilience
    options.RetryAttempts = 3;
    options.CircuitBreakerFailureThreshold = 3;
    options.CircuitBreakerCooldown = TimeSpan.FromSeconds(60);
    
    // Timeouts
    options.RequestTimeout = TimeSpan.FromSeconds(30);
    options.StateExpiration = TimeSpan.FromMinutes(10);
});

Multi-Client Support

For managing multiple Schwab accounts or environments:

// Use SQLite storage instead of JSON
builder.Services.AddSchwabOAuthWithSqlite(
    databasePath: "schwab_tokens.db",
    options =>
    {
        // Same options as AddSchwabOAuth
    });

Events

Subscribe to token refresh events:

oauthService.TokenRefreshed += (sender, e) =>
{
    Console.WriteLine($"Token refreshed for {e.ClientId}. New expiration: {e.NewExpiresAt}");
};

oauthService.TokenRefreshFailed += (sender, e) =>
{
    Console.WriteLine($"Token refresh failed for {e.ClientId}: {e.Exception.Message}");
    
    if (e.RequiresReauthorization)
    {
        Console.WriteLine("Full re-authorization required!");
    }
};

Cross-Platform Notes

Linux

  • Requires xdg-open for browser launch (pre-installed on most distros)
  • Token file permissions set to 600 automatically

macOS

  • First run may show firewall prompt for callback server (click "Allow")
  • Uses open command for browser launch
  • Token file permissions set to 600 automatically

Windows

  • Uses UseShellExecute for browser launch
  • Token file ACLs set to current user only

License

MIT License - see LICENSE file for details

Contributing

See CONTRIBUTING.md for guidelines

Support

For issues and questions, please visit: https://github.com/veenroid/SchwabSharp/issues

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated