{"id":30713039,"url":"https://github.com/kriasoft/oauth-callback","last_synced_at":"2026-01-24T23:19:45.405Z","repository":{"id":310041834,"uuid":"1038484305","full_name":"kriasoft/oauth-callback","owner":"kriasoft","description":"Lightweight OAuth 2.0 authorization code capture for CLI tools \u0026 desktop   apps. Works with Node.js, Deno, Bun. MCP SDK ready.","archived":false,"fork":false,"pushed_at":"2025-08-23T16:44:43.000Z","size":3581,"stargazers_count":5,"open_issues_count":16,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-24T06:22:54.231Z","etag":null,"topics":["authentication","authorization","bun","cli","deno","localhost","mcp","mcp-client","mcp-server","model-context-protocol","nodejs","oauth","oauth-callback-url","oauth2","rfc7591","sdk","typescript"],"latest_commit_sha":null,"homepage":"https://kriasoft.com/oauth-callback/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kriasoft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"koistya"}},"created_at":"2025-08-15T09:32:34.000Z","updated_at":"2025-08-23T16:44:47.000Z","dependencies_parsed_at":"2025-08-15T11:31:14.785Z","dependency_job_id":"b4c1b0c7-8519-49c9-b5d1-34402ecdda98","html_url":"https://github.com/kriasoft/oauth-callback","commit_stats":null,"previous_names":["kriasoft/oauth-callback"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/kriasoft/oauth-callback","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kriasoft%2Foauth-callback","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kriasoft%2Foauth-callback/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kriasoft%2Foauth-callback/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kriasoft%2Foauth-callback/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kriasoft","download_url":"https://codeload.github.com/kriasoft/oauth-callback/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kriasoft%2Foauth-callback/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273382042,"owners_count":25095368,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-03T02:00:09.631Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","authorization","bun","cli","deno","localhost","mcp","mcp-client","mcp-server","model-context-protocol","nodejs","oauth","oauth-callback-url","oauth2","rfc7591","sdk","typescript"],"created_at":"2025-09-03T03:10:35.528Z","updated_at":"2026-01-24T23:19:45.345Z","avatar_url":"https://github.com/kriasoft.png","language":"TypeScript","readme":"# OAuth Callback\n\n[![npm version](https://badge.fury.io/js/oauth-callback.svg)](https://badge.fury.io/js/oauth-callback)\n[![npm downloads](https://img.shields.io/npm/dm/oauth-callback.svg)](https://npmjs.com/package/oauth-callback)\n[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/kriasoft/oauth-callback/blob/main/LICENSE)\n[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)\n[![Run on Replit](https://img.shields.io/badge/Run%20on-Replit-orange.svg)](https://replit.com/@kriasoft/oauth-callback)\n\nA lightweight OAuth 2.0 callback handler for Node.js, Deno, and Bun with built-in browser flow and MCP SDK integration. Perfect for CLI tools, desktop applications, and development environments that need to capture OAuth authorization codes.\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/kriasoft/oauth-callback/main/examples/notion.gif\" alt=\"OAuth Callback Demo\" width=\"100%\" style=\"max-width: 800px; height: auto;\"\u003e\n\u003c/div\u003e\n\n## Features\n\n- 🚀 **Multi-runtime support** - Works with Node.js 18+, Deno, and Bun\n- 🔒 **Secure localhost-only server** for OAuth callbacks\n- 🤖 **MCP SDK integration** - Built-in OAuth provider for Model Context Protocol\n- ⚡ **Single dependency** - Only requires `open` for browser launching\n- 🎯 **TypeScript support** out of the box\n- 🛡️ **Comprehensive OAuth error handling** with detailed error classes\n- 🔄 **Automatic server cleanup** after callback\n- 💾 **Flexible token storage** - In-memory and file-based options\n- 🎪 **Clean success pages** with animated checkmark\n- 🎨 **Customizable HTML templates** with placeholder support\n- 🚦 **AbortSignal support** for programmatic cancellation\n- 📝 **Request logging and debugging** callbacks\n- 🌐 **Modern Web Standards APIs** (Request/Response/URL)\n\n## Installation\n\n```bash\nbun add oauth-callback open\n```\n\nOr with npm:\n\n```bash\nnpm install oauth-callback open\n```\n\n\u003e **Note:** The `open` package is optional but recommended for browser launching. If using pnpm, install it explicitly: `pnpm add open`\n\n## Quick Start\n\n```typescript\nimport open from \"open\";\nimport { getAuthCode, OAuthError } from \"oauth-callback\";\n\n// Simple usage - pass `open` to launch browser\nconst result = await getAuthCode({\n  authorizationUrl:\n    \"https://example.com/oauth/authorize?client_id=xxx\u0026redirect_uri=http://localhost:3000/callback\",\n  launch: open,\n});\nconsole.log(\"Authorization code:\", result.code);\n\n// MCP SDK integration - use specific import\nimport { browserAuth, fileStore } from \"oauth-callback/mcp\";\nconst authProvider = browserAuth({ launch: open, store: fileStore() });\n\n// Or via namespace import\nimport { mcp } from \"oauth-callback\";\nconst authProvider = mcp.browserAuth({ launch: open, store: mcp.fileStore() });\n```\n\n## Usage Examples\n\n### Basic OAuth Flow\n\n```typescript\nimport open from \"open\";\nimport { getAuthCode, OAuthError } from \"oauth-callback\";\n\nasync function authenticate() {\n  const authUrl =\n    \"https://github.com/login/oauth/authorize?\" +\n    new URLSearchParams({\n      client_id: \"your_client_id\",\n      redirect_uri: \"http://localhost:3000/callback\",\n      scope: \"user:email\",\n      state: \"random_state_string\",\n    });\n\n  try {\n    const result = await getAuthCode({\n      authorizationUrl: authUrl,\n      launch: open,\n    });\n    console.log(\"Authorization code:\", result.code);\n    console.log(\"State:\", result.state);\n\n    // Exchange code for access token\n    // ... your token exchange logic here\n  } catch (error) {\n    if (error instanceof OAuthError) {\n      console.error(\"OAuth error:\", error.error);\n      console.error(\"Description:\", error.error_description);\n    } else {\n      console.error(\"Unexpected error:\", error);\n    }\n  }\n}\n```\n\n### Custom Port Configuration\n\n```typescript\nimport open from \"open\";\nimport { getAuthCode } from \"oauth-callback\";\n\nconst result = await getAuthCode({\n  authorizationUrl: authUrl,\n  launch: open,\n  port: 8080, // Use custom port (default: 3000)\n  timeout: 60000, // Custom timeout in ms (default: 30000)\n});\n```\n\n### MCP SDK Integration\n\nThe `browserAuth()` function provides a drop-in OAuth provider for the Model Context Protocol SDK:\n\n```typescript\nimport { browserAuth, inMemoryStore } from \"oauth-callback/mcp\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\n\n// Create MCP-compatible OAuth provider\nconst authProvider = browserAuth({\n  port: 3000,\n  scope: \"read write\",\n  launch: open, // Opens browser for OAuth consent\n  store: inMemoryStore(), // Or fileStore() for persistence\n});\n\n// Use with MCP SDK transport\nconst transport = new StreamableHTTPClientTransport(\n  new URL(\"https://mcp.notion.com/mcp\"),\n  { authProvider },\n);\n\nconst client = new Client(\n  { name: \"my-app\", version: \"1.0.0\" },\n  { capabilities: {} },\n);\n\nawait client.connect(transport);\n```\n\n#### Token Storage Options\n\n```typescript\nimport { browserAuth, inMemoryStore, fileStore } from \"oauth-callback/mcp\";\n\n// Ephemeral storage (tokens lost on restart)\nconst ephemeralAuth = browserAuth({\n  launch: open,\n  store: inMemoryStore(),\n});\n\n// Persistent file storage (default: ~/.mcp/tokens.json)\nconst persistentAuth = browserAuth({\n  launch: open,\n  store: fileStore(),\n  storeKey: \"my-app-tokens\", // Namespace for multiple apps\n});\n\n// Custom file location\nconst customAuth = browserAuth({\n  launch: open,\n  store: fileStore(\"/path/to/tokens.json\"),\n});\n```\n\n#### Pre-configured Client Credentials\n\nIf you have pre-registered OAuth client credentials:\n\n```typescript\nconst authProvider = browserAuth({\n  clientId: \"your-client-id\",\n  clientSecret: \"your-client-secret\",\n  scope: \"read write\",\n  launch: open, // Opens browser for OAuth consent\n  store: fileStore(), // Persist tokens across sessions\n});\n```\n\n### Advanced Usage\n\n```typescript\nimport open from \"open\";\n\n// With custom HTML templates and logging\nconst result = await getAuthCode({\n  authorizationUrl: authUrl,\n  launch: open,\n  port: 3000,\n  hostname: \"127.0.0.1\", // Bind to specific IP\n  successHtml: \"\u003ch1\u003eSuccess! You can close this window.\u003c/h1\u003e\",\n  errorHtml: \"\u003ch1\u003eError: {{error_description}}\u003c/h1\u003e\",\n  onRequest: (req) =\u003e {\n    console.log(`Received request: ${req.method} ${req.url}`);\n  },\n});\n\n// With cancellation support\nconst controller = new AbortController();\n\n// Cancel after 10 seconds\nsetTimeout(() =\u003e controller.abort(), 10000);\n\ntry {\n  const result = await getAuthCode({\n    authorizationUrl: authUrl,\n    signal: controller.signal,\n  });\n} catch (error) {\n  if (error.message === \"Operation aborted\") {\n    console.log(\"Authorization was cancelled\");\n  }\n}\n```\n\n## API Reference\n\n### `getAuthCode(input)`\n\nStarts a local HTTP server to capture OAuth callbacks. Optionally launches the authorization URL via the `launch` callback.\n\n#### Parameters\n\n- `input` (string | GetAuthCodeOptions): Either a string containing the OAuth authorization URL, or an options object with:\n  - `authorizationUrl` (string): The OAuth authorization URL\n  - `port` (number): Port for the local server (default: 3000)\n  - `hostname` (string): Hostname to bind the server to (default: \"localhost\")\n  - `callbackPath` (string): URL path for the OAuth callback (default: \"/callback\")\n  - `timeout` (number): Timeout in milliseconds (default: 30000)\n  - `launch` (function): Optional callback to launch the authorization URL (e.g., `open`)\n  - `successHtml` (string): Custom HTML to display on successful authorization\n  - `errorHtml` (string): Custom HTML to display on authorization error\n  - `signal` (AbortSignal): AbortSignal for cancellation support\n  - `onRequest` (function): Callback fired when a request is received (for logging/debugging)\n\n#### Returns\n\nPromise that resolves to:\n\n```typescript\n{\n  code: string;        // Authorization code\n  state?: string;      // State parameter (if provided)\n  [key: string]: any;  // Additional query parameters\n}\n```\n\n#### Throws\n\n- `OAuthError`: When the OAuth provider returns an error (always thrown for OAuth errors)\n- `Error`: For timeout or other unexpected errors\n\n### `OAuthError`\n\nCustom error class for OAuth-specific errors.\n\n```typescript\nclass OAuthError extends Error {\n  error: string; // OAuth error code\n  error_description?: string; // Human-readable error description\n  error_uri?: string; // URI with error information\n}\n```\n\n### `browserAuth(options)`\n\nAvailable from `oauth-callback/mcp`. Creates an MCP SDK-compatible OAuth provider for browser-based flows. Handles Dynamic Client Registration (DCR), token storage, and automatic refresh.\n\n#### Parameters\n\n- `options` (BrowserAuthOptions): Configuration object with:\n  - `port` (number): Port for callback server (default: 3000)\n  - `hostname` (string): Hostname to bind to (default: \"localhost\")\n  - `callbackPath` (string): URL path for OAuth callback (default: \"/callback\")\n  - `scope` (string): OAuth scopes to request\n  - `clientId` (string): Pre-registered client ID (optional)\n  - `clientSecret` (string): Pre-registered client secret (optional)\n  - `store` (TokenStore): Token storage implementation (default: inMemoryStore())\n  - `storeKey` (string): Storage key for tokens (default: \"mcp-tokens\")\n  - `launch` (function): Callback to launch auth URL (e.g., `open`)\n  - `authTimeout` (number): Authorization timeout in ms (default: 300000)\n  - `successHtml` (string): Custom success page HTML\n  - `errorHtml` (string): Custom error page HTML\n  - `onRequest` (function): Request logging callback\n\n#### Returns\n\nOAuthClientProvider compatible with MCP SDK transports.\n\n### `inMemoryStore()`\n\nAvailable from `oauth-callback/mcp`. Creates an ephemeral in-memory token store. Tokens are lost when the process exits.\n\n#### Returns\n\nTokenStore implementation for temporary token storage.\n\n### `fileStore(filepath?)`\n\nAvailable from `oauth-callback/mcp`. Creates a persistent file-based token store.\n\n#### Parameters\n\n- `filepath` (string): Optional custom file path (default: `~/.mcp/tokens.json`)\n\n#### Returns\n\nTokenStore implementation for persistent token storage.\n\n## How It Works\n\n```\n┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐\n│  Your App   │────▶│Local Server │────▶│   Browser   │────▶│OAuth Server │\n│             │     │ :3000       │     │             │     │             │\n│ getAuthCode │     │             │◀────│  Callback   │◀────│  Redirect   │\n│     ▼       │◀────│ Returns     │     │ /callback   │     │  with code  │\n│   {code}    │     │ auth code   │     │             │     │             │\n└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘\n```\n\n1. **Server Creation** — Spins up a temporary localhost HTTP server\n2. **Browser Launch** — Opens the authorization URL (if `launch` callback provided)\n3. **User Authorization** — User grants permission on the OAuth provider's page\n4. **Callback Capture** — Provider redirects to localhost with the authorization code\n5. **Cleanup** — Server closes automatically, code is returned to your app\n\n## Security Considerations\n\n- **Localhost-only binding** — Server rejects non-local connections\n- **Ephemeral server** — Shuts down immediately after receiving the callback\n- **No credential logging** — Tokens and codes are never written to logs\n- **State parameter support** — Pass and validate state to prevent CSRF attacks\n- **Configurable timeouts** — Server auto-terminates if callback isn't received\n- **PKCE compatible** — Works with authorization servers that require PKCE\n\n## Running the Examples\n\n### Interactive Demo (No Setup Required)\n\nTry the library instantly with the built-in demo that includes a mock OAuth server:\n\n```bash\n# Run the demo - no credentials needed!\nbun run example:demo\n\n# Run without opening browser (for CI/testing)\nbun run examples/demo.ts --no-browser\n```\n\nThe demo showcases:\n\n- Dynamic client registration (simplified OAuth 2.0 DCR)\n- Complete authorization flow with mock provider\n- Multiple scenarios (success, access denied, invalid scope)\n- Custom HTML templates for success/error pages\n- Token exchange and API usage simulation\n\n### Real OAuth Examples\n\n#### GitHub OAuth\n\nFor testing with GitHub OAuth:\n\n```bash\n# Set up GitHub OAuth App credentials\nexport GITHUB_CLIENT_ID=\"your_client_id\"\nexport GITHUB_CLIENT_SECRET=\"your_client_secret\"\n\n# Run the GitHub example\nbun run example:github\n```\n\nThis example demonstrates:\n\n- Setting up OAuth with GitHub\n- Handling the authorization callback\n- Exchanging the code for an access token\n- Using the token to fetch user information\n\n#### Notion MCP with Dynamic Client Registration\n\nFor testing with Notion's Model Context Protocol server:\n\n```bash\n# No credentials needed - uses Dynamic Client Registration!\nbun run example:notion\n```\n\nThis example demonstrates:\n\n- Dynamic Client Registration (OAuth 2.0 DCR) - no pre-configured client ID/secret needed\n- Integration with Model Context Protocol (MCP) servers\n- Automatic client registration with the authorization server\n- Using `browserAuth()` provider with MCP SDK's `StreamableHTTPClientTransport`\n- Token persistence with `inMemoryStore()` for ephemeral sessions\n\n## Development\n\n```bash\n# Install dependencies\nbun install\n\n# Run tests\nbun test\n\n# Build\nbun run build\n\n# Run documentation locally\nbun run docs:dev        # Start VitePress dev server at http://localhost:5173\n\n# Run examples\nbun run example:demo    # Interactive demo\nbun run example:github  # GitHub OAuth example\nbun run example:notion  # Notion MCP example with Dynamic Client Registration\n```\n\n## Requirements\n\n- Node.js 18+ (for native Request/Response support), Deno, or Bun 1.0+\n- A registered OAuth application with a provider\n- Redirect URI configured as `http://localhost:[port]/callback`\n\n## Common Issues\n\n### Port Already in Use\n\nIf port 3000 is already in use, specify a different port:\n\n```typescript\nconst result = await getAuthCode({\n  authorizationUrl: authUrl,\n  launch: open,\n  port: 8080,\n});\n```\n\n### Firewall Warnings\n\nOn first run, your OS may show a firewall warning. Allow the connection for localhost only.\n\n### Browser Doesn't Open\n\nIf the browser doesn't open automatically, manually navigate to the authorization URL.\n\n## Contributing\n\nContributions are welcome! See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for setup instructions.\n\n**Maintainers wanted** — we're looking for people to help maintain this project. If interested, reach out on [Discord](https://discord.gg/bSsv7XM) or open an issue.\n\n## License\n\nThis project is released under the MIT License. Feel free to use it in your projects, modify it to suit your needs, and share it with others. We believe in open source and hope this tool makes OAuth integration easier for everyone!\n\n## Related Projects\n\n- [**MCP Client Generator**](https://github.com/kriasoft/mcp-client-gen) - Generate TypeScript clients from MCP server specifications. Perfect companion for building MCP-enabled applications with OAuth support ([npm](https://www.npmjs.com/package/mcp-client-gen)).\n- [**React Starter Kit**](https://github.com/kriasoft/react-starter-kit) - Full-stack React application template with authentication, including OAuth integration examples.\n\n## Backers\n\nSupport this project by becoming a backer. Your logo will show up here with a link to your website.\n\n\u003ca href=\"https://reactstarter.com/b/1\"\u003e\u003cimg src=\"https://reactstarter.com/b/1.png\" height=\"60\" /\u003e\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u003ca href=\"https://reactstarter.com/b/2\"\u003e\u003cimg src=\"https://reactstarter.com/b/2.png\" height=\"60\" /\u003e\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u003ca href=\"https://reactstarter.com/b/3\"\u003e\u003cimg src=\"https://reactstarter.com/b/3.png\" height=\"60\" /\u003e\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u003ca href=\"https://reactstarter.com/b/4\"\u003e\u003cimg src=\"https://reactstarter.com/b/4.png\" height=\"60\" /\u003e\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u003ca href=\"https://reactstarter.com/b/5\"\u003e\u003cimg src=\"https://reactstarter.com/b/5.png\" height=\"60\" /\u003e\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u003ca href=\"https://reactstarter.com/b/6\"\u003e\u003cimg src=\"https://reactstarter.com/b/6.png\" height=\"60\" /\u003e\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u003ca href=\"https://reactstarter.com/b/7\"\u003e\u003cimg src=\"https://reactstarter.com/b/7.png\" height=\"60\" /\u003e\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u003ca href=\"https://reactstarter.com/b/8\"\u003e\u003cimg src=\"https://reactstarter.com/b/8.png\" height=\"60\" /\u003e\u003c/a\u003e\n\n## Support\n\nFound a bug or have a question? Please open an issue on the [GitHub issue tracker](https://github.com/kriasoft/oauth-callback/issues) and we'll be happy to help. If this project saves you time and you'd like to support its continued development, consider [becoming a sponsor](https://github.com/sponsors/koistya). Every bit of support helps maintain and improve this tool for the community. Thank you!\n","funding_links":["https://github.com/sponsors/koistya"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkriasoft%2Foauth-callback","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkriasoft%2Foauth-callback","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkriasoft%2Foauth-callback/lists"}