Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Auth Providers

БесплатноНе проверен

Token providers for MCP ABAP ADT auth-broker

GitHubEmbed

Описание

Token providers for MCP ABAP ADT auth-broker

README

Stand With Ukraine

Token providers for MCP ABAP ADT auth-broker.

This package provides token provider implementations for the @mcp-abap-adt/auth-broker package.

Installation

npm install @mcp-abap-adt/auth-providers

Overview

This package implements the ITokenProvider interface from @mcp-abap-adt/interfaces:

  • AuthorizationCodeProvider - Uses browser-based OAuth2 authorization code flow (user token)
  • ClientCredentialsProvider - Uses client_credentials grant type (no browser required)

Providers are configured via constructor; getTokens() takes no parameters and handles refresh/login internally.

Responsibilities and Design Principles

Core Development Principle

Interface-Only Communication: This package follows a fundamental development principle: all interactions with external dependencies happen ONLY through interfaces. The code knows NOTHING beyond what is defined in the interfaces.

This means:

  • Does not know about concrete implementation classes from other packages
  • Does not know about internal data structures or methods not defined in interfaces
  • Does not make assumptions about implementation behavior beyond interface contracts
  • Does not access properties or methods not explicitly defined in interfaces

This principle ensures:

  • Loose coupling: Providers are decoupled from concrete implementations in other packages
  • Flexibility: New implementations can be added without modifying providers
  • Testability: Easy to mock dependencies for testing
  • Maintainability: Changes to implementations don't affect providers

Package Responsibilities

This package is responsible for:

  1. Implementing token provider interface: Provides concrete implementations of ITokenProvider interface defined in @mcp-abap-adt/interfaces
  2. Token acquisition: Handles OAuth2 flows (browser-based, refresh token, client credentials) to obtain JWT tokens
  3. Token validation: Validates JWT locally by checking exp claim (no HTTP requests)
  4. OAuth2 flows: Manages browser-based OAuth2 authorization code flow and refresh token flow

What This Package Does

  • Implements ITokenProvider: Provides concrete implementations (AuthorizationCodeProvider, ClientCredentialsProvider)
  • Handles OAuth2 flows: Browser-based OAuth2, refresh token, and client credentials grant types
  • Obtains tokens: Makes HTTP requests to UAA endpoints to obtain JWT tokens
  • Validates tokens: Validates JWT locally by checking exp claim (no HTTP requests)
  • Returns tokens: Returns ITokenResult with authorizationToken and optional refreshToken

What This Package Does NOT Do

  • Does NOT store tokens: Token storage is handled by @mcp-abap-adt/auth-stores
  • Does NOT orchestrate authentication: Token lifecycle management is handled by @mcp-abap-adt/auth-broker
  • Does NOT know about service keys: Service key loading is handled by stores
  • Does NOT manage sessions: Session management is handled by stores
  • Does NOT return serviceUrl if unknown: Providers may not return serviceUrl because they only handle token acquisition, not connection configuration

External Dependencies

This package interacts with external packages ONLY through interfaces:

  • @mcp-abap-adt/auth-broker: Uses interfaces (ITokenProvider, IAuthorizationConfig) - does not know about AuthBroker implementation
  • @mcp-abap-adt/logger: Uses Logger interface for logging - does not know about concrete logger implementation
  • @mcp-abap-adt/connection: Uses connection utilities for token validation - interacts through well-defined functions
  • No direct dependencies on stores: All interactions with stores happen through interfaces passed by consumers

Usage

Basic Usage

import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { AuthorizationCodeProvider, ClientCredentialsProvider } from '@mcp-abap-adt/auth-providers';

// User token via authorization_code (browser flow)
const authCodeBroker = new AuthBroker({
  tokenProvider: new AuthorizationCodeProvider({
    uaaUrl: 'https://...',
    clientId: '...',
    clientSecret: '...',
    browser: 'system',
  }),
});

// Service token via client_credentials (no browser)
const clientCredsBroker = new AuthBroker({
  tokenProvider: new ClientCredentialsProvider({
    uaaUrl: 'https://...',
    clientId: '...',
    clientSecret: '...',
  }),
}, 'none');

Browser modes (AuthorizationCodeProvider)

The browser option controls how the authorization URL is opened:

Mode Behaviour
system (default) Open the OS default browser
chrome / edge / firefox Open a specific browser
auto Try to open a browser; on failure, print the URL and wait
none / headless Do not open a browser — print the URL and wait for the code (SSH / remote / containers)

In none/headless mode the authorization URL is always shown, even when no logger is supplied (it falls back to stderr, never stdout, so stdio-based RPC transports are not corrupted).

Manual paste (none / headless)

Login can complete through any of three channels — whichever finishes first wins:

  1. Automatic callbackGET /callback?code=... on http://localhost:<redirectPort>. Works when the browser is on the same machine as the process.
  2. Paste form — open http://<host>:<redirectPort>/ and paste the code (or the whole redirected URL). Works when the browser is on a different machine, since the callback server listens on all interfaces.
  3. Terminal paste — paste the code on stdin and press Enter. Only active when process.stdin.isTTY (stdin is never consumed under a stdio RPC transport).

The exported extractCode(input) helper accepts a bare code, code=..., or a full redirected URL.

SSO Providers

This package also includes SSO providers for OIDC and SAML2, plus a small factory for DI-friendly creation.

Available providers:

  • OidcBrowserProvider (authorization code + PKCE)
  • OidcDeviceFlowProvider
  • OidcPasswordProvider
  • OidcTokenExchangeProvider
  • Saml2BearerProvider (SAML assertion exchange)
  • Saml2PureProvider (returns SAMLResponse as token)

Factory example:

import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { SsoProviderFactory } from '@mcp-abap-adt/auth-providers';

const tokenProvider = SsoProviderFactory.create({
  protocol: 'oidc',
  flow: 'browser',
  config: {
    issuerUrl: 'https://example-idp/.well-known/openid-configuration',
    clientId: '...',
    clientSecret: '...',
    scopes: ['openid', 'profile', 'email'],
    browser: 'system',
  },
});

const broker = new AuthBroker({ tokenProvider }, 'none');

OIDC browser example (manual code + explicit endpoints):

import { OidcBrowserProvider } from '@mcp-abap-adt/auth-providers';

const provider = new OidcBrowserProvider({
  clientId: '...',
  tokenEndpoint: 'https://issuer/oauth/token',
  authorizationEndpoint: 'https://issuer/oauth/authorize',
  authorizationCode: '<paste-code-here>',
  redirectUri: 'urn:ietf:wg:oauth:2.0:oob',
});

SAML bearer example (manual flow):

import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { Saml2BearerProvider } from '@mcp-abap-adt/auth-providers';

const provider = new Saml2BearerProvider({
  assertionFlow: 'manual',
  idpSsoUrl: 'https://idp.example.com/sso',
  spEntityId: 'my-sp-entity',
  uaaUrl: 'https://uaa.example.com',
  clientId: '...',
  clientSecret: '...',
});

const broker = new AuthBroker({ tokenProvider: provider }, 'none');

SAML bearer example (headless, assertion provider):

import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { Saml2BearerProvider } from '@mcp-abap-adt/auth-providers';

const provider = new Saml2BearerProvider({
  assertionFlow: 'assertion',
  assertionProvider: async () => {
    return getSamlResponseFromSsoProxy();
  },
  uaaUrl: 'https://uaa.example.com',
  clientId: '...',
  clientSecret: '...',
});

const broker = new AuthBroker({ tokenProvider: provider }, 'none');

Pure SAML example (cookie-based):

import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { Saml2PureProvider } from '@mcp-abap-adt/auth-providers';

const provider = new Saml2PureProvider({
  assertionFlow: 'manual',
  idpSsoUrl: 'https://idp.example.com/sso',
  spEntityId: 'my-sp-entity',
  // Convert SAMLResponse to session cookies for SAP (implementation-specific)
  cookieProvider: async (samlResponse) => {
    return exchangeSamlForCookies(samlResponse);
  },
});

const broker = new AuthBroker({ tokenProvider: provider }, 'none');

With Stores

Important: BTP and ABAP are different entities:

  • BTP (base BTP) - uses BtpServiceKeyStore and BtpSessionStore (without sapUrl)
  • ABAP - uses AbapServiceKeyStore and AbapSessionStore (with sapUrl)
import { AuthBroker } from '@mcp-abap-adt/auth-broker';
import { AuthorizationCodeProvider, ClientCredentialsProvider } from '@mcp-abap-adt/auth-providers';
import { 
  XsuaaServiceKeyStore, 
  XsuaaSessionStore,
  BtpServiceKeyStore,
  BtpSessionStore,
  AbapServiceKeyStore,
  AbapSessionStore 
} from '@mcp-abap-adt/auth-stores';

// XSUAA provider with stores (client_credentials or auth code)
const xsuaaServiceKeyStore = new XsuaaServiceKeyStore('/path/to/service-keys');
const xsuaaSessionStore = new XsuaaSessionStore('/path/to/sessions');

const xsuaaBroker = new AuthBroker({
  serviceKeyStore: xsuaaServiceKeyStore,
  sessionStore: xsuaaSessionStore,
  tokenProvider: new ClientCredentialsProvider({
    uaaUrl: 'https://...',
    clientId: '...',
    clientSecret: '...',
  }),
}, 'none');

// BTP provider with stores (base BTP, without sapUrl)
const btpServiceKeyStore = new BtpServiceKeyStore('/path/to/service-keys');
const btpSessionStore = new BtpSessionStore('/path/to/sessions');

const btpBroker = new AuthBroker({
  serviceKeyStore: btpServiceKeyStore,
  sessionStore: btpSessionStore,
  tokenProvider: new AuthorizationCodeProvider({
    uaaUrl: 'https://...',
    clientId: '...',
    clientSecret: '...',
    browser: 'system',
  }),
});

// ABAP provider with stores (with sapUrl)
const abapServiceKeyStore = new AbapServiceKeyStore('/path/to/service-keys');
const abapSessionStore = new AbapSessionStore('/path/to/sessions');

// Use custom port if running alongside other services (e.g., proxy on port 3001)
const abapBroker = new AuthBroker({
  serviceKeyStore: abapServiceKeyStore,
  sessionStore: abapSessionStore,
  tokenProvider: new AuthorizationCodeProvider({
    uaaUrl: 'https://...',
    clientId: '...',
    clientSecret: '...',
    browser: 'system',
    redirectPort: 4001,
  }), // Custom port to avoid conflicts
});

Token Providers

AuthorizationCodeProvider

Uses browser-based OAuth2 flow or refresh token:

import { AuthorizationCodeProvider } from '@mcp-abap-adt/auth-providers';

const provider = new AuthorizationCodeProvider({
  uaaUrl: 'https://...authentication...hana.ondemand.com',
  clientId: '...',
  clientSecret: '...',
  browser: 'system',
});

// If refreshToken is provided here, uses refresh flow (no browser)
// Otherwise, opens browser for OAuth2 authorization
const result = await provider.getTokens();

// result.authorizationToken contains the JWT token
// result.refreshToken contains refresh token (if browser flow was used)

ClientCredentialsProvider

Uses client_credentials grant type - no browser interaction required:

import { ClientCredentialsProvider } from '@mcp-abap-adt/auth-providers';

const provider = new ClientCredentialsProvider({
  uaaUrl: 'https://...authentication...hana.ondemand.com',
  clientId: '...',
  clientSecret: '...',
});

const result = await provider.getTokens();

// result.authorizationToken contains the JWT token
// result.refreshToken is undefined (client_credentials doesn't provide refresh tokens)

Note: The browserAuthPort parameter (default: 3001) configures the OAuth callback server port. If the requested port is already in use, an error will be thrown. You must specify a different port or free the port before starting authentication. The server properly closes all connections and frees the port after authentication completes, ensuring no lingering port occupation.

Timeout: Browser authentication has a 30-second timeout to prevent blocking the consumer. If authentication is not completed within 30 seconds, the operation will fail with a timeout error. This prevents the provider from hanging indefinitely when the user doesn't complete authentication.

Process Termination Handling: The OAuth callback server registers cleanup handlers for SIGTERM, SIGINT, SIGHUP, and exit signals. This ensures ports are properly freed even when MCP clients (like Cline) terminate the process before authentication completes. This is especially important for stdio servers where the client may kill the process at any time. On Windows, the SIGBREAK signal (Ctrl+Break) is also handled.

Cross-Platform Browser Support: The browser authentication works across Linux, macOS, and Windows:

  • Linux: Automatically sets DISPLAY=:0 if neither DISPLAY nor WAYLAND_DISPLAY environment variables are set. Supports multiple browser executable names (google-chrome, google-chrome-stable, chromium, chromium-browser for Chrome; firefox, firefox-esr for Firefox).
  • Windows: Uses proper cmd /c start "" syntax for reliable browser opening.
  • macOS: Uses native open -a command.

Headless Mode (SSH/Remote): For environments without a display (SSH sessions, Docker, CI/CD), use browser: 'headless':

const result = await provider.getTokens();

In headless mode, the authentication URL is logged and the server waits for the user to complete authentication manually. The user can open the URL on any machine and the callback will be received by the server.

Browser Options:

  • 'system' (default): Opens system default browser
  • 'headless': Logs URL, waits for manual callback (SSH/remote)
  • 'none': Logs URL, immediately rejects (automated tests)
  • 'chrome', 'edge', 'firefox': Opens specific browser

Token Validation

Providers can perform local JWT validation by checking the exp (expiration) claim:

const isValid = await provider.validateToken(token, serviceUrl);
  • No HTTP requests are made to the SAP server
  • Returns true if token has valid JWT format and exp is in the future (with 60s buffer)
  • Returns false if token is expired, invalid format, or will expire within 60 seconds
  • Network issues (ECONNREFUSED, timeout) do NOT trigger token refresh
  • HTTP errors (401/403) are handled by retry mechanism in makeAdtRequest wrapper
// Local validation (no HTTP)
const provider = new AuthorizationCodeProvider({
  uaaUrl: 'https://...authentication...hana.ondemand.com',
  clientId: '...',
  clientSecret: '...',
});
const isValid = await provider.validateToken(token);  // serviceUrl optional
// Checks JWT exp claim locally, no network request

This approach prevents unnecessary token refresh and browser authentication when:

  • Server is unreachable (ECONNREFUSED, timeout)
  • Network is slow or unstable
  • Running in offline/disconnected mode

Token Refresh

Providers handle refresh automatically inside getTokens(). No separate refresh methods are needed.

try {
  const result = await provider.getTokens();
  // Returns new access token and refresh token (if available)
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Missing fields:', error.missingFields);
  } else if (error instanceof RefreshError) {
    console.error('Browser auth failed:', error.cause);
  }
}

Error Handling

The package provides typed error classes for better error handling:

import {
  TokenProviderError,
  ValidationError,
  RefreshError,
  SessionDataError,
  ServiceKeyError,
  BrowserAuthError,
} from '@mcp-abap-adt/auth-providers';

try {
  const result = await provider.getTokens();
} catch (error) {
  if (error instanceof ValidationError) {
    // provider config validation failed
    console.error('Missing required fields:', error.missingFields);
    console.error('Error code:', error.code); // 'VALIDATION_ERROR'
  } else if (error instanceof RefreshError) {
    // Token refresh operation failed
    console.error('Refresh failed:', error.message);
    console.error('Original error:', error.cause);
    console.error('Error code:', error.code); // 'REFRESH_ERROR'
  } else if (error instanceof BrowserAuthError) {
    // Browser authentication failed
    console.error('Browser auth failed:', error.cause);
  }
}

Error Types:

  • TokenProviderError - Base class with code: string property
  • ValidationError - provider config validation failed, includes missingFields: string[]
  • RefreshError - Token refresh failed, includes cause?: Error
  • SessionDataError - Session data invalid, includes missingFields: string[]
  • ServiceKeyError - Service key data invalid, includes missingFields: string[]
  • BrowserAuthError - Browser auth failed, includes cause?: Error

All error codes are defined in @mcp-abap-adt/interfaces package as TOKEN_PROVIDER_ERROR_CODES.

Testing

The package includes both unit tests (with mocks) and integration tests (with real files and services).

Unit Tests

npm test

Integration Tests

Integration tests work with real files from tests/test-config.yaml:

  1. Copy tests/test-config.yaml.template to tests/test-config.yaml
  2. Fill in real destination name
  3. Run tests - integration tests will use real services if configured
# Destination name (used for service key file: <destination>.json and session file: <destination>.env)
destination: "trial"  # Example: "trial" -> looks for trial.json and trial.env

# Optional: Destination directory (base directory for service keys and sessions)
# If not specified, uses default platform paths:
#   Unix: ~/.config/mcp-abap-adt
#   Windows: %USERPROFILE%\Documents\mcp-abap-adt
# Uncomment and set if you need a custom path:
# destination_dir: ~/.config/mcp-abap-adt

Integration tests will skip if test-config.yaml is not configured or contains placeholder values.

Test Scenarios:

  • Scenario 1 & 2: Token lifecycle - login via browser and reuse token from previous scenario
  • Scenario 3: Expired session + expired refresh token - provider should re-authenticate via browser
  • Token validation: Explicit validation of token expiration in all scenarios

Note:

  • Integration tests use AbapServiceKeyStore and AbapSessionStore for loading service keys and sessions
  • Tests may open a browser for authentication if no refresh token is available. This is expected behavior.
  • Each test scenario uses a unique port (3101, 3102, 3103) to avoid port conflicts
  • Tests use browser: 'system' for interactive authentication (not 'none')

Debug Logging

To enable detailed logging during tests or runtime, set environment variables:

# Enable logging for auth providers (short name)
DEBUG_PROVIDER=true npm test

# Or use long name (backward compatibility)
DEBUG_AUTH_PROVIDERS=true npm test

# Or enable via general DEBUG variable
DEBUG=true npm test

# Or include in DEBUG list
DEBUG=provider npm test
# Or
DEBUG=auth-providers npm test

# Set log level (debug, info, warn, error)
LOG_LEVEL=debug npm test

Logging uses @mcp-abap-adt/logger package with structured logging:

  • Token exchange stages (what we send, what we receive)
  • Token information (lengths, previews, expiration)
  • Token validation checks (expiration, validity)
  • Errors with details

Example output:

[INFO] ℹ️ [browserAuth] Exchanging code for token...
[INFO] ℹ️ Tokens received: accessToken(2263 chars), refreshToken(34 chars)
[DEBUG] 🐛 [BaseTokenProvider] Token validation check {"expiresAt":"2025-12-25 11:08:15 UTC","isValid":true}
[INFO] ℹ️ [browserAuth] Authorization URL: https://.../oauth/authorize?...
[INFO] ℹ️ [browserAuth] Browser: system

Logging Features:

  • Token Formatting: Tokens are logged in truncated format (start...end) for security
  • Date Formatting: Expiration dates are displayed in readable format (YYYY-MM-DD HH:MM:SS UTC) instead of ISO format
  • Browser Information: Logs browser type and authorization URL for debugging
  • Token Lifecycle: Detailed logging of token acquisition, validation, and refresh operations

Dependencies

  • @mcp-abap-adt/interfaces (^0.2.2) - Interface definitions and error code constants
  • axios - HTTP client
  • express - OAuth2 callback server
  • open - Browser opening utility

License

MIT

from github.com/fr0ster/mcp-abap-adt-auth-providers

Установить Auth Providers в Claude Desktop, Claude Code, Cursor

Рекомендуется · одна команда, все IDE
unyly install auth-providers

Ставит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.

Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh

Или настроить вручную

Выполни в терминале:

claude mcp add auth-providers -- npx -y @mcp-abap-adt/auth-providers

FAQ

Auth Providers MCP бесплатный?

Да, Auth Providers MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Auth Providers?

Нет, Auth Providers работает без API-ключей и переменных окружения.

Auth Providers — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Auth Providers в Claude Desktop, Claude Code или Cursor?

Открой Auth Providers на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Auth Providers with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории development