@babamba2/mcp-abap-adt-auth-providers
v1.0.5
Published
Token providers for MCP ABAP ADT auth-broker (fork of @mcp-abap-adt/auth-providers by fr0ster)
Readme
@mcp-abap-adt/auth-providers
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-providersOverview
This package implements the ITokenProvider interface from @mcp-abap-adt/interfaces:
- AuthorizationCodeProvider - Uses browser-based OAuth2 authorization code flow (user token)
- ClientCredentialsProvider - Uses
client_credentialsgrant 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:
- Implementing token provider interface: Provides concrete implementations of
ITokenProviderinterface defined in@mcp-abap-adt/interfaces - Token acquisition: Handles OAuth2 flows (browser-based, refresh token, client credentials) to obtain JWT tokens
- Token validation: Validates JWT locally by checking exp claim (no HTTP requests)
- 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
ITokenResultwithauthorizationTokenand optionalrefreshToken
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
serviceUrlif unknown: Providers may not returnserviceUrlbecause 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 aboutAuthBrokerimplementation@mcp-abap-adt/logger: UsesLoggerinterface 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');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)OidcDeviceFlowProviderOidcPasswordProviderOidcTokenExchangeProviderSaml2BearerProvider(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
BtpServiceKeyStoreandBtpSessionStore(withoutsapUrl) - ABAP - uses
AbapServiceKeyStoreandAbapSessionStore(withsapUrl)
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=:0if neitherDISPLAYnorWAYLAND_DISPLAYenvironment variables are set. Supports multiple browser executable names (google-chrome,google-chrome-stable,chromium,chromium-browserfor Chrome;firefox,firefox-esrfor Firefox). - Windows: Uses proper
cmd /c start ""syntax for reliable browser opening. - macOS: Uses native
open -acommand.
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
trueif token has valid JWT format andexpis in the future (with 60s buffer) - Returns
falseif 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
makeAdtRequestwrapper
// 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 requestThis 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 withcode: stringpropertyValidationError- provider config validation failed, includesmissingFields: string[]RefreshError- Token refresh failed, includescause?: ErrorSessionDataError- Session data invalid, includesmissingFields: string[]ServiceKeyError- Service key data invalid, includesmissingFields: string[]BrowserAuthError- Browser auth failed, includescause?: 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 testIntegration Tests
Integration tests work with real files from tests/test-config.yaml:
- Copy
tests/test-config.yaml.templatetotests/test-config.yaml - Fill in real destination name
- 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-adtIntegration 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
AbapServiceKeyStoreandAbapSessionStorefor 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 testLogging 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: systemLogging 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 constantsaxios- HTTP clientexpress- OAuth2 callback serveropen- Browser opening utility
License
MIT
