| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| export function createPrivateKeyJwtAuth(options) {
|
| return async (_headers, params, url, metadata) => {
|
|
|
| if (typeof globalThis.crypto === 'undefined') {
|
| throw new TypeError('crypto is not available, please ensure you add have Web Crypto API support for older Node.js versions (see https://github.com/modelcontextprotocol/typescript-sdk#nodejs-web-crypto-globalthiscrypto-compatibility)');
|
| }
|
| const jose = await import('jose');
|
| const audience = String(options.audience ?? metadata?.issuer ?? url);
|
| const lifetimeSeconds = options.lifetimeSeconds ?? 300;
|
| const now = Math.floor(Date.now() / 1000);
|
| const jti = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
| const baseClaims = {
|
| iss: options.issuer,
|
| sub: options.subject,
|
| aud: audience,
|
| exp: now + lifetimeSeconds,
|
| iat: now,
|
| jti
|
| };
|
| const claims = options.claims ? { ...baseClaims, ...options.claims } : baseClaims;
|
|
|
| const alg = options.alg;
|
| let key;
|
| if (typeof options.privateKey === 'string') {
|
| if (alg.startsWith('RS') || alg.startsWith('ES') || alg.startsWith('PS')) {
|
| key = await jose.importPKCS8(options.privateKey, alg);
|
| }
|
| else if (alg.startsWith('HS')) {
|
| key = new TextEncoder().encode(options.privateKey);
|
| }
|
| else {
|
| throw new Error(`Unsupported algorithm ${alg}`);
|
| }
|
| }
|
| else if (options.privateKey instanceof Uint8Array) {
|
| if (alg.startsWith('HS')) {
|
| key = options.privateKey;
|
| }
|
| else {
|
|
|
| key = await jose.importPKCS8(new TextDecoder().decode(options.privateKey), alg);
|
| }
|
| }
|
| else {
|
|
|
| key = await jose.importJWK(options.privateKey, alg);
|
| }
|
|
|
| const assertion = await new jose.SignJWT(claims)
|
| .setProtectedHeader({ alg, typ: 'JWT' })
|
| .setIssuer(options.issuer)
|
| .setSubject(options.subject)
|
| .setAudience(audience)
|
| .setIssuedAt(now)
|
| .setExpirationTime(now + lifetimeSeconds)
|
| .setJti(jti)
|
| .sign(key);
|
| params.set('client_assertion', assertion);
|
| params.set('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer');
|
| };
|
| }
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| export class ClientCredentialsProvider {
|
| constructor(options) {
|
| this._clientInfo = {
|
| client_id: options.clientId,
|
| client_secret: options.clientSecret
|
| };
|
| this._clientMetadata = {
|
| client_name: options.clientName ?? 'client-credentials-client',
|
| redirect_uris: [],
|
| grant_types: ['client_credentials'],
|
| token_endpoint_auth_method: 'client_secret_basic',
|
| scope: options.scope
|
| };
|
| }
|
| get redirectUrl() {
|
| return undefined;
|
| }
|
| get clientMetadata() {
|
| return this._clientMetadata;
|
| }
|
| clientInformation() {
|
| return this._clientInfo;
|
| }
|
| saveClientInformation(info) {
|
| this._clientInfo = info;
|
| }
|
| tokens() {
|
| return this._tokens;
|
| }
|
| saveTokens(tokens) {
|
| this._tokens = tokens;
|
| }
|
| redirectToAuthorization() {
|
| throw new Error('redirectToAuthorization is not used for client_credentials flow');
|
| }
|
| saveCodeVerifier() {
|
|
|
| }
|
| codeVerifier() {
|
| throw new Error('codeVerifier is not used for client_credentials flow');
|
| }
|
| prepareTokenRequest(scope) {
|
| const params = new URLSearchParams({ grant_type: 'client_credentials' });
|
| if (scope)
|
| params.set('scope', scope);
|
| return params;
|
| }
|
| }
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| export class PrivateKeyJwtProvider {
|
| constructor(options) {
|
| this._clientInfo = {
|
| client_id: options.clientId
|
| };
|
| this._clientMetadata = {
|
| client_name: options.clientName ?? 'private-key-jwt-client',
|
| redirect_uris: [],
|
| grant_types: ['client_credentials'],
|
| token_endpoint_auth_method: 'private_key_jwt',
|
| scope: options.scope
|
| };
|
| this.addClientAuthentication = createPrivateKeyJwtAuth({
|
| issuer: options.clientId,
|
| subject: options.clientId,
|
| privateKey: options.privateKey,
|
| alg: options.algorithm,
|
| lifetimeSeconds: options.jwtLifetimeSeconds
|
| });
|
| }
|
| get redirectUrl() {
|
| return undefined;
|
| }
|
| get clientMetadata() {
|
| return this._clientMetadata;
|
| }
|
| clientInformation() {
|
| return this._clientInfo;
|
| }
|
| saveClientInformation(info) {
|
| this._clientInfo = info;
|
| }
|
| tokens() {
|
| return this._tokens;
|
| }
|
| saveTokens(tokens) {
|
| this._tokens = tokens;
|
| }
|
| redirectToAuthorization() {
|
| throw new Error('redirectToAuthorization is not used for client_credentials flow');
|
| }
|
| saveCodeVerifier() {
|
|
|
| }
|
| codeVerifier() {
|
| throw new Error('codeVerifier is not used for client_credentials flow');
|
| }
|
| prepareTokenRequest(scope) {
|
| const params = new URLSearchParams({ grant_type: 'client_credentials' });
|
| if (scope)
|
| params.set('scope', scope);
|
| return params;
|
| }
|
| }
|
| |
| |
| |
| |
| |
| |
|
|
| export class StaticPrivateKeyJwtProvider {
|
| constructor(options) {
|
| this._clientInfo = {
|
| client_id: options.clientId
|
| };
|
| this._clientMetadata = {
|
| client_name: options.clientName ?? 'static-private-key-jwt-client',
|
| redirect_uris: [],
|
| grant_types: ['client_credentials'],
|
| token_endpoint_auth_method: 'private_key_jwt',
|
| scope: options.scope
|
| };
|
| const assertion = options.jwtBearerAssertion;
|
| this.addClientAuthentication = async (_headers, params) => {
|
| params.set('client_assertion', assertion);
|
| params.set('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer');
|
| };
|
| }
|
| get redirectUrl() {
|
| return undefined;
|
| }
|
| get clientMetadata() {
|
| return this._clientMetadata;
|
| }
|
| clientInformation() {
|
| return this._clientInfo;
|
| }
|
| saveClientInformation(info) {
|
| this._clientInfo = info;
|
| }
|
| tokens() {
|
| return this._tokens;
|
| }
|
| saveTokens(tokens) {
|
| this._tokens = tokens;
|
| }
|
| redirectToAuthorization() {
|
| throw new Error('redirectToAuthorization is not used for client_credentials flow');
|
| }
|
| saveCodeVerifier() {
|
|
|
| }
|
| codeVerifier() {
|
| throw new Error('codeVerifier is not used for client_credentials flow');
|
| }
|
| prepareTokenRequest(scope) {
|
| const params = new URLSearchParams({ grant_type: 'client_credentials' });
|
| if (scope)
|
| params.set('scope', scope);
|
| return params;
|
| }
|
| }
|
| |