@mcp-abap-adt/auth-broker
v0.1.9
Published
JWT authentication broker for MCP ABAP ADT - manages tokens based on destination headers
Downloads
1,124
Maintainers
Readme
@mcp-abap-adt/auth-broker
JWT authentication broker for MCP ABAP ADT server. Manages authentication tokens based on destination headers, automatically loading tokens from .env files and refreshing them using service keys when needed.
Features
- 🔐 Destination-based Authentication: Load tokens based on
x-mcp-destinationheader - 📁 Environment File Support: Automatically loads tokens from
{destination}.envfiles - 🔄 Automatic Token Refresh: Refreshes expired tokens using service keys from
{destination}.jsonfiles - ✅ Token Validation: Validates tokens by testing connection to SAP system
- 💾 Token Caching: In-memory caching for improved performance
- 🔧 Configurable Base Path: Customize where
.envand.jsonfiles are stored
Installation
npm install @mcp-abap-adt/auth-brokerUsage
import {
AuthBroker,
AbapServiceKeyStore,
AbapSessionStore,
SafeAbapSessionStore,
BtpTokenProvider
} from '@mcp-abap-adt/auth-broker';
// Use default file-based stores (current working directory)
const broker = new AuthBroker();
// Use custom file-based stores with specific paths
const broker = new AuthBroker({
serviceKeyStore: new AbapServiceKeyStore(['/path/to/destinations']),
sessionStore: new AbapSessionStore(['/path/to/destinations']),
tokenProvider: new BtpTokenProvider(),
}, 'chrome');
// Use safe in-memory session store (data lost after restart)
const broker = new AuthBroker({
serviceKeyStore: new AbapServiceKeyStore(['/path/to/destinations']),
sessionStore: new SafeAbapSessionStore(), // In-memory, secure
tokenProvider: new BtpTokenProvider(),
});
// Get token for destination (loads from .env, validates, refreshes if needed)
const token = await broker.getToken('TRIAL');
// Force refresh token using service key
const newToken = await broker.refreshToken('TRIAL');Configuration
Environment Variables
AUTH_BROKER_PATH- Colon/semicolon-separated paths for searching.envand.jsonfiles (default: current working directory)DEBUG_AUTH_LOG- Set totrueto enable debug logging (default:false)
File Structure
Environment File for ABAP ({destination}.env)
For ABAP connections, use SAP_* environment variables:
SAP_URL=https://your-system.abap.us10.hana.ondemand.com
SAP_CLIENT=100
SAP_JWT_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
SAP_REFRESH_TOKEN=refresh_token_string
SAP_UAA_URL=https://your-account.authentication.us10.hana.ondemand.com
SAP_UAA_CLIENT_ID=client_id
SAP_UAA_CLIENT_SECRET=client_secretEnvironment File for XSUAA ({destination}.env)
For XSUAA connections (reduced scope), use XSUAA_* environment variables:
XSUAA_MCP_URL=https://your-mcp-server.cfapps.eu10.hana.ondemand.com
XSUAA_JWT_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
XSUAA_REFRESH_TOKEN=refresh_token_string
XSUAA_UAA_URL=https://your-account.authentication.eu10.hana.ondemand.com
XSUAA_UAA_CLIENT_ID=client_id
XSUAA_UAA_CLIENT_SECRET=client_secretNote: XSUAA_MCP_URL is optional - it's not part of authentication, only needed for making requests. The token and UAA credentials are sufficient for authentication.
Environment File for BTP ({destination}.env)
For BTP connections (full scope for ABAP systems), use BTP_* environment variables:
BTP_ABAP_URL=https://your-system.abap.us10.hana.ondemand.com
BTP_JWT_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
BTP_REFRESH_TOKEN=refresh_token_string
BTP_UAA_URL=https://your-account.authentication.eu10.hana.ondemand.com
BTP_UAA_CLIENT_ID=client_id
BTP_UAA_CLIENT_SECRET=client_secret
BTP_SAP_CLIENT=100
BTP_LANGUAGE=ENNote: BTP_ABAP_URL is required - it's the ABAP system URL. All parameters (except tokens) come from service key.
Service Key File for ABAP ({destination}.json)
Standard ABAP service key format:
{
"url": "https://your-system.abap.us10.hana.ondemand.com",
"uaa": {
"url": "https://your-account.authentication.us10.hana.ondemand.com",
"clientid": "your_client_id",
"clientsecret": "your_client_secret"
}
}Service Key File for XSUAA ({destination}.json)
Direct XSUAA service key format (from BTP):
{
"url": "https://your-account.authentication.eu10.hana.ondemand.com",
"apiurl": "https://api.authentication.eu10.hana.ondemand.com",
"clientid": "your_client_id",
"clientsecret": "your_client_secret"
}Note: For XSUAA service keys, apiurl is prioritized over url for UAA authorization if present.
XSUAA vs BTP Authentication
This package supports two types of BTP authentication:
XSUAA (Reduced Scope)
- Purpose: Access BTP services with limited scopes
- Service Key: Contains only UAA credentials (no ABAP URL)
- Session Store:
XsuaaSessionStore(usesXSUAA_*environment variables) - Authentication: Client credentials grant type (no browser required)
- MCP URL: Optional, provided separately (from YAML config
mcp_url, parameter, or request header) - Use Case: Accessing BTP services like MCP servers with reduced permissions
BTP (Full Scope for ABAP)
- Purpose: Access ABAP systems with full roles and scopes
- Service Key: Contains UAA credentials and ABAP URL
- Session Store:
BtpSessionStore(usesBTP_*environment variables) - Authentication: Browser-based OAuth2 (like ABAP) or refresh token
- ABAP URL: Required, from service key or YAML configuration
- Use Case: Accessing ABAP systems in BTP with full permissions
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 (e.g.,
AbapSessionStore,BtpTokenProvider) - 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:
AuthBrokeris decoupled from concrete implementations - Flexibility: New implementations can be added without modifying
AuthBroker - Testability: Easy to mock dependencies for testing
- Maintainability: Changes to implementations don't affect
AuthBroker
Package Responsibilities
The @mcp-abap-adt/auth-broker package defines interfaces and provides orchestration logic for authentication. It does not implement concrete storage or token acquisition mechanisms - these are provided by separate packages (@mcp-abap-adt/auth-stores, @mcp-abap-adt/auth-providers).
What AuthBroker Does
- Orchestrates authentication flows: Coordinates token retrieval, validation, and refresh using provided stores and providers
- Manages token lifecycle: Handles token caching, validation, and automatic refresh
- Works with interfaces only: Uses
IServiceKeyStore,ISessionStore, andITokenProviderinterfaces without knowing concrete implementations - Delegates to providers: Calls
tokenProvider.getConnectionConfig()to obtain tokens and connection configuration - Delegates to stores: Uses
sessionStore.setConnectionConfig()to save tokens and connection configuration
What AuthBroker Does NOT Do
- Does NOT know about
serviceUrl:AuthBrokerdoes not know whether a specificISessionStoreimplementation requiresserviceUrlor not. It simply passes theIConnectionConfigreturned bytokenProvidertosessionStore.setConnectionConfig() - Does NOT merge configurations:
AuthBrokerdoes not mergeserviceUrlfrom service keys with connection config from token providers. This is the responsibility of the consumer or the session store implementation - Does NOT implement storage: File I/O, parsing, and storage logic are handled by concrete store implementations from
@mcp-abap-adt/auth-stores - Does NOT implement token acquisition: OAuth2 flows, refresh token logic, and client credentials are handled by concrete provider implementations from
@mcp-abap-adt/auth-providers
Consumer Responsibilities
The consumer (application using AuthBroker) is responsible for:
Selecting appropriate implementations: Choose the correct
IServiceKeyStore,ISessionStore, andITokenProviderimplementations based on the use case:- ABAP systems: Use
AbapServiceKeyStore,AbapSessionStore(orSafeAbapSessionStore), andBtpTokenProvider - BTP systems: Use
AbapServiceKeyStore,BtpSessionStore(orSafeBtpSessionStore), andBtpTokenProvider - XSUAA services: Use
XsuaaServiceKeyStore,XsuaaSessionStore(orSafeXsuaaSessionStore), andXsuaaTokenProvider
- ABAP systems: Use
Ensuring complete configuration: If a session store requires
serviceUrl(e.g.,AbapSessionStorerequiressapUrl), the consumer must ensure that:- The session is created with
serviceUrlbefore callingAuthBroker.getToken(), OR - The session store implementation handles
serviceUrlretrieval internally (e.g., fromserviceKeyStore)
- The session is created with
Understanding store requirements: Different session store implementations have different requirements:
AbapSessionStore: RequiressapUrl(maps toserviceUrlinIConnectionConfig)BtpSessionStore: Does not requireserviceUrl(usesmcpUrlinstead)XsuaaSessionStore: Does not requireserviceUrl(MCP URL is optional)
Store Responsibilities
Concrete ISessionStore implementations are responsible for:
- Handling their own data format: Each store knows its internal data format (e.g.,
AbapSessionData,BtpBaseSessionData) - Converting between formats: Converting between
IConfig/IConnectionConfigand internal storage format - Managing required fields: If a store requires
serviceUrl(e.g.,AbapSessionStore), it should:- Retrieve it from
serviceKeyStoreif not provided inIConnectionConfig, OR - Use existing value from current session if available, OR
- Throw an error if neither is available (depending on implementation)
- Retrieve it from
Provider Responsibilities
Concrete ITokenProvider implementations are responsible for:
- Obtaining tokens: Using OAuth2 flows, refresh tokens, or client credentials to obtain JWT tokens
- Returning connection config: Returning
IConnectionConfigwithauthorizationTokenand optionallyserviceUrl(if known) - Not returning
serviceUrlif unknown: Providers likeBtpTokenProvidermay not returnserviceUrlbecause they only handle token acquisition, not connection configuration
Design Principles
- Interface-Only Communication (Core Principle): All interactions with external dependencies happen ONLY through interfaces. The code knows NOTHING beyond what is defined in the interfaces (see Core Development Principle above)
- Dependency Inversion Principle (DIP):
AuthBrokerdepends on abstractions (IServiceKeyStore,ISessionStore,ITokenProvider), not concrete implementations - Single Responsibility: Each component has a single, well-defined responsibility:
AuthBroker: Orchestration and token lifecycle managementISessionStore: Session data storage and retrievalITokenProvider: Token acquisitionIServiceKeyStore: Service key storage and retrieval
- Interface Segregation: Interfaces are focused and minimal, containing only what's necessary for their specific purpose
- Open/Closed Principle: New store and provider implementations can be added without modifying
AuthBroker
Example: Why AuthBroker Doesn't Handle serviceUrl
Consider this scenario:
BtpTokenProvider.getConnectionConfig()returnsIConnectionConfigwithauthorizationTokenbut withoutserviceUrl(because it only handles token acquisition)AbapSessionStore.setConnectionConfig()requiressapUrl(which maps toserviceUrl)
If AuthBroker tried to merge serviceUrl from serviceKeyStore, it would:
- Violate the DIP by knowing about specific store requirements
- Break the abstraction -
AuthBrokershouldn't know thatAbapSessionStoreneedsserviceUrl - Create coupling between
AuthBrokerand concrete implementations
Instead, the consumer or AbapSessionStore itself should handle this:
- Option 1: Consumer retrieves
serviceUrlfromserviceKeyStoreand ensures it's in the session before callingAuthBroker.getToken() - Option 2:
AbapSessionStore.setConnectionConfig()retrievesserviceUrlfromserviceKeyStoreinternally if not provided - Option 3:
AbapSessionStore.setConnectionConfig()uses existingsapUrlfrom current session if available
API
AuthBroker
Constructor
new AuthBroker(
stores?: {
serviceKeyStore?: IServiceKeyStore;
sessionStore?: ISessionStore;
tokenProvider?: ITokenProvider;
},
browser?: string,
logger?: ILogger
)stores- Optional object with custom storage implementations:serviceKeyStore- Store for service keys (default:AbapServiceKeyStore())sessionStore- Store for session data (default:AbapSessionStore())tokenProvider- Token provider for token acquisition (default:BtpTokenProvider())- Available implementations:
- ABAP:
AbapServiceKeyStore(searchPaths?),AbapSessionStore(searchPaths?),SafeAbapSessionStore(),BtpTokenProvider() - XSUAA (reduced scope):
XsuaaServiceKeyStore(searchPaths?),XsuaaSessionStore(searchPaths?),SafeXsuaaSessionStore(),XsuaaTokenProvider() - BTP (full scope for ABAP):
AbapServiceKeyStore(searchPaths?),BtpSessionStore(searchPaths?),SafeBtpSessionStore(),BtpTokenProvider()
- ABAP:
browser- Optional browser name for authentication (chrome,edge,firefox,system,none). Default:system- For XSUAA, browser is not used (client_credentials grant type) - use
'none'
- For XSUAA, browser is not used (client_credentials grant type) - use
logger- Optional logger instance. If not provided, uses default logger
Methods
getToken(destination: string): Promise<string>
Gets authentication token for destination. Tries to load from session store, validates it, and refreshes if needed using a fallback chain:
- Check session: Load token from session store and validate it
- Try refresh token: If refresh token is available, attempt to refresh using it (via tokenProvider)
- Try UAA (client_credentials): Attempt to get token using UAA credentials (via tokenProvider)
- Try browser authentication: Attempt browser-based OAuth2 flow using service key (via tokenProvider)
- Throw error: If all authentication methods failed
Note: Token validation is performed only when checking existing session. Tokens obtained through refresh/UAA/browser authentication are not validated before being saved.
refreshToken(destination: string): Promise<string>
Force refresh token for destination using service key from {destination}.json file.
clearCache(destination: string): void
Clear cached token for specific destination.
clearAllCache(): void
Clear all cached tokens.
Token Providers
The package uses ITokenProvider interface for token acquisition. Two implementations are available:
XsuaaTokenProvider- For XSUAA authentication (reduced scope)- Uses client_credentials grant type
- No browser interaction required
- No refresh token provided
BtpTokenProvider- For BTP/ABAP authentication (full scope)- Uses browser-based OAuth2 flow (if no refresh token)
- Uses refresh token if available
- Provides refresh token for future use
Example Usage:
import {
AuthBroker,
XsuaaServiceKeyStore,
XsuaaSessionStore,
XsuaaTokenProvider,
BtpTokenProvider
} from '@mcp-abap-adt/auth-broker';
// XSUAA authentication (no browser needed)
const xsuaaBroker = new AuthBroker({
serviceKeyStore: new XsuaaServiceKeyStore(['/path/to/keys']),
sessionStore: new XsuaaSessionStore(['/path/to/sessions']),
tokenProvider: new XsuaaTokenProvider(),
}, 'none');
// BTP authentication (browser or refresh token)
const btpBroker = new AuthBroker({
serviceKeyStore: new AbapServiceKeyStore(['/path/to/keys']),
sessionStore: new BtpSessionStore(['/path/to/sessions']),
tokenProvider: new BtpTokenProvider(),
});Utility Script
Generate .env files from service keys:
npm run generate-env <destination> [service-key-path] [session-path]Examples:
# Generate .env from service key (auto-detect paths)
npm run generate-env mcp
# Specify paths explicitly
npm run generate-env mcp ./mcp.json ./mcp.env
# Use absolute paths
npm run generate-env TRIAL ~/.config/mcp-abap-adt/service-keys/TRIAL.json ~/.config/mcp-abap-adt/sessions/TRIAL.envThe script automatically detects service key type (ABAP or XSUAA) and uses the appropriate authentication flow:
- ABAP: Opens browser for OAuth2 authorization code flow
- XSUAA: Uses client_credentials grant type (no browser required)
Testing
Tests are located in src/__tests__/ and use Jest as the test runner.
Running Tests
# Run all tests
npm test
# Run specific test file (all tests in that file)
npm test -- getToken.test.ts
npm test -- refreshToken.test.ts
# Run specific test by name/pattern
npm test -- getToken.test.ts -t "Test 1"
npm test -- getToken.test.ts -t "Test 2"
npm test -- getToken.test.ts -t "Test 3"
# Run test group (e.g., all getToken tests)
npm test -- getToken.test.ts
# Note: Test 2 requires Test 1 to pass first (test1Passed flag)
# To run Test 2 alone, you may need to run all tests in the file:
npm test -- getToken.test.tsTest Structure
Tests are designed to run sequentially (guaranteed by maxWorkers: 1 and maxConcurrency: 1 in jest.config.js):
- Test 1: Verifies error handling for non-existent destination (
NO_EXISTS)- Requires:
NO_EXISTS.jsonshould NOT exist
- Requires:
- Test 2: Tests browser authentication when service key exists but
.envfile doesn't- Requires:
TRIAL.jsonmust exist,TRIAL.envshould NOT exist - Will open browser for OAuth authentication
- Requires:
- Test 3: Tests token refresh using existing
.envfile- Requires:
TRIAL.jsonandTRIAL.envmust exist - Can run independently if
.envfile exists (created manually or by Test 2)
- Requires:
Test Setup
- Copy
tests/test-config.yaml.templatetotests/test-config.yaml - Fill in configuration values (paths, destinations, MCP URL for XSUAA)
- Place service key files in configured
service_keys_dir:{destination}.jsonfor ABAP tests (e.g.,trial.json){btp_destination}.jsonfor XSUAA tests (e.g.,btp.json)
Tests will automatically skip if required files are missing or configuration contains placeholders.
Documentation
Complete documentation is available in the docs/ directory:
- Architecture - System architecture and design decisions
- Development - Testing methodology and development roadmap
- Development Roadmap - Development roadmap and future plans
- Installation - Installation and setup guide
- Usage - API reference and usage examples
See docs/README.md for the complete documentation index.
Contributors
Thank you to all contributors! See CONTRIBUTORS.md for the complete list.
License
MIT
