OAuth Client
The @leanmcp/auth/client module provides a complete OAuth 2.1 client implementation for MCP applications. It handles browser-based authentication flows with PKCE, secure token storage, and automatic token refresh.
Features
OAuth 2.1 with PKCE Secure authorization code flow with Proof Key for Code Exchange
Token Storage Pluggable storage backends: memory, file, or OS keychain
Auto Refresh Automatic token refresh before expiration
Dynamic Registration RFC 7591 Dynamic Client Registration support
Installation
npm install @leanmcp/auth
For file-based storage with encryption:
npm install @leanmcp/auth
For OS keychain storage:
npm install @leanmcp/auth keytar
Quick Start
import { OAuthClient } from '@leanmcp/auth/client' ;
import { FileStorage } from '@leanmcp/auth/storage' ;
// Create client with file-based token storage
const client = new OAuthClient ({
serverUrl: 'https://api.example.com' ,
authorizationEndpoint: 'https://api.example.com/oauth/authorize' ,
tokenEndpoint: 'https://api.example.com/oauth/token' ,
clientId: 'my-app' ,
scopes: [ 'openid' , 'profile' ],
storage: new FileStorage ({
filePath: '~/.myapp/tokens.json' ,
prettyPrint: true ,
}),
pkceEnabled: true ,
autoRefresh: true ,
});
// Authenticate (opens browser)
const tokens = await client . authenticate ();
console . log ( 'Authenticated!' , tokens . access_token );
// Get valid token (auto-refreshes if needed)
const token = await client . getValidToken ();
// Use token in API calls
const response = await fetch ( 'https://api.example.com/data' , {
headers: { Authorization: `Bearer ${ token } ` },
});
OAuthClient
The main client class for OAuth 2.1 flows.
Constructor Options
interface OAuthClientOptions {
/** Base URL of the OAuth server */
serverUrl : string ;
/** Authorization endpoint URL */
authorizationEndpoint : string ;
/** Token endpoint URL */
tokenEndpoint : string ;
/** Token storage backend */
storage : TokenStorage ;
/** Client ID (required if not using dynamic registration) */
clientId ?: string ;
/** Client secret (for confidential clients) */
clientSecret ?: string ;
/** OAuth scopes to request */
scopes ?: string [];
/** Enable PKCE (default: true) */
pkceEnabled ?: boolean ;
/** Automatically refresh tokens before expiry (default: false) */
autoRefresh ?: boolean ;
/** Callback URL for browser flow (default: http://localhost:PORT/callback) */
redirectUri ?: string ;
/** Port for local callback server (default: random available port) */
callbackPort ?: number ;
}
Methods
authenticate()
Initiates the OAuth flow by opening a browser window for user authentication.
const tokens = await client . authenticate ();
// Returns: { access_token, token_type, expires_in, refresh_token?, scope? }
Flow:
Generates PKCE code verifier and challenge (if enabled)
Opens browser to authorization endpoint
Starts local HTTP server to receive callback
Exchanges authorization code for tokens
Stores tokens in configured storage
getValidToken()
Returns a valid access token, refreshing if necessary.
const token = await client . getValidToken ();
// Returns: string (access token)
If the current token is expired and a refresh token is available, it will automatically refresh. Throws if no valid token is available.
getTokens()
Returns the current stored tokens without refreshing.
const tokens = await client . getTokens ();
// Returns: TokenSet | null
logout()
Clears stored tokens.
Token Storage
The @leanmcp/auth/storage module provides pluggable storage backends for tokens.
MemoryStorage
Stores tokens in memory. Tokens are lost when the process exits.
import { MemoryStorage } from '@leanmcp/auth/storage' ;
const storage = new MemoryStorage ();
const client = new OAuthClient ({
// ...
storage ,
});
Use cases:
Development and testing
Short-lived CLI commands
Serverless functions (tokens passed externally)
FileStorage
Stores tokens in a JSON file with optional encryption.
import { FileStorage } from '@leanmcp/auth/storage' ;
const storage = new FileStorage ({
/** Path to token file (supports ~ for home directory) */
filePath: '~/.myapp/tokens.json' ,
/** Encryption key (optional, enables AES-256-GCM encryption) */
encryptionKey? : string ,
/** Pretty-print JSON (default: false) */
prettyPrint? : boolean ,
});
Example with encryption:
const storage = new FileStorage ({
filePath: '~/.myapp/tokens.json' ,
encryptionKey: process . env . TOKEN_ENCRYPTION_KEY ,
});
If using encryption, store the encryption key securely (e.g., environment variable).
Losing the key means losing access to stored tokens.
KeychainStorage
Stores tokens in the OS secure keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service).
import { KeychainStorage } from '@leanmcp/auth/storage' ;
const storage = new KeychainStorage ({
/** Service name in keychain */
service: 'my-app' ,
/** Account name in keychain */
account: 'oauth-tokens' ,
});
Requires the keytar package: npm install keytar
Use cases:
Desktop CLI applications
Developer tools
Any application where OS-level security is preferred
Custom Storage
Implement the TokenStorage interface for custom backends:
import type { TokenStorage , TokenSet } from '@leanmcp/auth/storage' ;
class RedisStorage implements TokenStorage {
constructor ( private redis : RedisClient , private key : string ) {}
async get () : Promise < TokenSet | null > {
const data = await this . redis . get ( this . key );
return data ? JSON . parse ( data ) : null ;
}
async set ( tokens : TokenSet ) : Promise < void > {
await this . redis . set ( this . key , JSON . stringify ( tokens ));
}
async clear () : Promise < void > {
await this . redis . del ( this . key );
}
}
PKCE Flow
PKCE (Proof Key for Code Exchange) is enabled by default and required by the MCP OAuth specification.
The client automatically:
Generates a cryptographically random code_verifier
Creates the code_challenge using SHA-256
Sends the challenge with the authorization request
Sends the verifier with the token exchange
// PKCE is enabled by default
const client = new OAuthClient ({
serverUrl: 'https://api.example.com' ,
// ...
pkceEnabled: true , // default
});
Token Refresh
Automatic Refresh
When autoRefresh is enabled, getValidToken() automatically refreshes expired tokens:
const client = new OAuthClient ({
// ...
autoRefresh: true ,
});
// Always returns a valid token
const token = await client . getValidToken ();
Manual Refresh
You can also manually refresh tokens:
const newTokens = await client . refreshTokens ();
Complete Example
Here’s a complete CLI application that authenticates with an OAuth server:
import { OAuthClient } from '@leanmcp/auth/client' ;
import { FileStorage } from '@leanmcp/auth/storage' ;
const SERVER_URL = 'https://api.example.com' ;
async function main () {
const storage = new FileStorage ({
filePath: '~/.myapp/tokens.json' ,
prettyPrint: true ,
});
const client = new OAuthClient ({
serverUrl: SERVER_URL ,
authorizationEndpoint: ` ${ SERVER_URL } /oauth/authorize` ,
tokenEndpoint: ` ${ SERVER_URL } /oauth/token` ,
storage ,
clientId: 'my-cli-app' ,
scopes: [ 'openid' , 'profile' , 'read:data' ],
pkceEnabled: true ,
autoRefresh: true ,
});
// Check for existing tokens
const existing = await client . getTokens ();
if ( existing ?. access_token ) {
console . log ( 'Using existing session' );
} else {
console . log ( 'Opening browser for authentication...' );
await client . authenticate ();
console . log ( 'Authenticated!' );
}
// Make authenticated API call
const token = await client . getValidToken ();
const response = await fetch ( ` ${ SERVER_URL } /api/user` , {
headers: { Authorization: `Bearer ${ token } ` },
});
const user = await response . json ();
console . log ( 'User:' , user );
}
main (). catch ( console . error );
API Reference
TokenSet
interface TokenSet {
access_token : string ;
token_type : string ;
expires_in ?: number ;
refresh_token ?: string ;
scope ?: string ;
expires_at ?: number ; // Unix timestamp
}
TokenStorage Interface
interface TokenStorage {
get () : Promise < TokenSet | null >;
set ( tokens : TokenSet ) : Promise < void >;
clear () : Promise < void >;
}