@microsoft/agents-hosting 1.6.0-beta.9.gce9d8facd2 → 1.7.0-beta.1.g8bcd4f11a7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/package.json +4 -9
  2. package/dist/src/agent-client/agentClient.js +2 -2
  3. package/dist/src/agent-client/agentClient.js.map +1 -1
  4. package/dist/src/agent-client/agentResponseHandler.js +2 -2
  5. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  6. package/dist/src/app/agentApplication.js +28 -2
  7. package/dist/src/app/agentApplication.js.map +1 -1
  8. package/dist/src/app/agentApplicationOptions.d.ts +10 -0
  9. package/dist/src/app/attachmentDownloader.js +5 -10
  10. package/dist/src/app/attachmentDownloader.js.map +1 -1
  11. package/dist/src/app/auth/authorizationManager.d.ts +4 -0
  12. package/dist/src/app/auth/authorizationManager.js +94 -67
  13. package/dist/src/app/auth/authorizationManager.js.map +1 -1
  14. package/dist/src/app/auth/handlers/azureBotAuthorization.js +2 -2
  15. package/dist/src/app/auth/handlers/azureBotAuthorization.js.map +1 -1
  16. package/dist/src/app/proactive/createConversationOptionsBuilder.js +10 -6
  17. package/dist/src/app/proactive/createConversationOptionsBuilder.js.map +1 -1
  18. package/dist/src/app/proactive/proactive.d.ts +1 -0
  19. package/dist/src/app/proactive/proactive.js +30 -15
  20. package/dist/src/app/proactive/proactive.js.map +1 -1
  21. package/dist/src/app/streaming/streamingResponse.d.ts +11 -0
  22. package/dist/src/app/streaming/streamingResponse.js +21 -0
  23. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  24. package/dist/src/app/teamsAttachmentDownloader.js +6 -14
  25. package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
  26. package/dist/src/auth/MemoryCache.d.ts +2 -1
  27. package/dist/src/auth/MemoryCache.js +7 -1
  28. package/dist/src/auth/MemoryCache.js.map +1 -1
  29. package/dist/src/auth/authConfiguration.d.ts +2 -143
  30. package/dist/src/auth/authConfiguration.js +336 -257
  31. package/dist/src/auth/authConfiguration.js.map +1 -1
  32. package/dist/src/auth/authConstants.d.ts +7 -7
  33. package/dist/src/auth/authConstants.js.map +1 -1
  34. package/dist/src/auth/connectionManager.d.ts +92 -0
  35. package/dist/src/auth/{msalConnectionManager.js → connectionManager.js} +48 -33
  36. package/dist/src/auth/connectionManager.js.map +1 -0
  37. package/dist/src/auth/index.d.ts +5 -3
  38. package/dist/src/auth/index.js +5 -3
  39. package/dist/src/auth/index.js.map +1 -1
  40. package/dist/src/auth/jwt-middleware.d.ts +6 -0
  41. package/dist/src/auth/jwt-middleware.js +34 -2
  42. package/dist/src/auth/jwt-middleware.js.map +1 -1
  43. package/dist/src/auth/msal/msalConnectionManager.d.ts +17 -0
  44. package/dist/src/auth/msal/msalConnectionManager.js +50 -0
  45. package/dist/src/auth/msal/msalConnectionManager.js.map +1 -0
  46. package/dist/src/auth/msal/msalConnectionSettings.d.ts +68 -0
  47. package/dist/src/auth/msal/msalConnectionSettings.js +7 -0
  48. package/dist/src/auth/msal/msalConnectionSettings.js.map +1 -0
  49. package/dist/src/auth/{msalTokenCredential.d.ts → msal/msalTokenCredential.d.ts} +1 -1
  50. package/dist/src/auth/{msalTokenCredential.js → msal/msalTokenCredential.js} +2 -2
  51. package/dist/src/auth/msal/msalTokenCredential.js.map +1 -0
  52. package/dist/src/auth/{msalTokenProvider.d.ts → msal/msalTokenProvider.d.ts} +30 -3
  53. package/dist/src/auth/msal/msalTokenProvider.js +733 -0
  54. package/dist/src/auth/msal/msalTokenProvider.js.map +1 -0
  55. package/dist/src/auth/settings.d.ts +336 -0
  56. package/dist/src/auth/settings.js +202 -0
  57. package/dist/src/auth/settings.js.map +1 -0
  58. package/dist/src/auth/sidecar/sidecarAuthProvider.d.ts +74 -0
  59. package/dist/src/auth/sidecar/sidecarAuthProvider.js +181 -0
  60. package/dist/src/auth/sidecar/sidecarAuthProvider.js.map +1 -0
  61. package/dist/src/auth/sidecar/sidecarConnectionSettings.d.ts +65 -0
  62. package/dist/src/auth/sidecar/sidecarConnectionSettings.js +7 -0
  63. package/dist/src/auth/sidecar/sidecarConnectionSettings.js.map +1 -0
  64. package/dist/src/auth/sidecar/sidecarHttpClient.d.ts +95 -0
  65. package/dist/src/auth/sidecar/sidecarHttpClient.js +406 -0
  66. package/dist/src/auth/sidecar/sidecarHttpClient.js.map +1 -0
  67. package/dist/src/auth/sidecar/sidecarModels.d.ts +110 -0
  68. package/dist/src/auth/sidecar/sidecarModels.js +48 -0
  69. package/dist/src/auth/sidecar/sidecarModels.js.map +1 -0
  70. package/dist/src/auth/sidecar/sidecarTokenExpiry.d.ts +13 -0
  71. package/dist/src/auth/sidecar/sidecarTokenExpiry.js +39 -0
  72. package/dist/src/auth/sidecar/sidecarTokenExpiry.js.map +1 -0
  73. package/dist/src/cloudAdapter.d.ts +58 -1
  74. package/dist/src/cloudAdapter.js +233 -53
  75. package/dist/src/cloudAdapter.js.map +1 -1
  76. package/dist/src/connector-client/connectorClient.d.ts +12 -8
  77. package/dist/src/connector-client/connectorClient.js +99 -82
  78. package/dist/src/connector-client/connectorClient.js.map +1 -1
  79. package/dist/src/errorHelper.js +181 -7
  80. package/dist/src/errorHelper.js.map +1 -1
  81. package/dist/src/getProductInfo.d.ts +23 -0
  82. package/dist/src/getProductInfo.js +71 -1
  83. package/dist/src/getProductInfo.js.map +1 -1
  84. package/dist/src/headerPropagation.d.ts +17 -2
  85. package/dist/src/headerPropagation.js +46 -24
  86. package/dist/src/headerPropagation.js.map +1 -1
  87. package/dist/src/httpClient.d.ts +60 -0
  88. package/dist/src/httpClient.js +173 -0
  89. package/dist/src/httpClient.js.map +1 -0
  90. package/dist/src/index.d.ts +3 -2
  91. package/dist/src/index.js +8 -2
  92. package/dist/src/index.js.map +1 -1
  93. package/dist/src/oauth/userTokenClient.d.ts +17 -4
  94. package/dist/src/oauth/userTokenClient.js +119 -80
  95. package/dist/src/oauth/userTokenClient.js.map +1 -1
  96. package/dist/src/observability/traces.d.ts +11 -4
  97. package/dist/src/observability/traces.js +39 -7
  98. package/dist/src/observability/traces.js.map +1 -1
  99. package/dist/src/utils/env.d.ts +50 -0
  100. package/dist/src/utils/env.js +113 -0
  101. package/dist/src/utils/env.js.map +1 -0
  102. package/dist/src/utils.d.ts +10 -0
  103. package/dist/src/utils.js +18 -0
  104. package/dist/src/utils.js.map +1 -0
  105. package/package.json +4 -9
  106. package/src/agent-client/agentClient.ts +2 -2
  107. package/src/agent-client/agentResponseHandler.ts +2 -2
  108. package/src/app/agentApplication.ts +30 -3
  109. package/src/app/agentApplicationOptions.ts +11 -0
  110. package/src/app/attachmentDownloader.ts +6 -8
  111. package/src/app/auth/authorizationManager.ts +84 -41
  112. package/src/app/auth/handlers/azureBotAuthorization.ts +2 -2
  113. package/src/app/proactive/createConversationOptionsBuilder.ts +8 -4
  114. package/src/app/proactive/proactive.ts +39 -23
  115. package/src/app/streaming/streamingResponse.ts +24 -1
  116. package/src/app/teamsAttachmentDownloader.ts +7 -11
  117. package/src/auth/MemoryCache.ts +7 -1
  118. package/src/auth/authConfiguration.ts +350 -356
  119. package/src/auth/authConstants.ts +7 -7
  120. package/src/auth/{msalConnectionManager.ts → connectionManager.ts} +65 -44
  121. package/src/auth/index.ts +5 -3
  122. package/src/auth/jwt-middleware.ts +32 -2
  123. package/src/auth/msal/msalConnectionManager.ts +55 -0
  124. package/src/auth/msal/msalConnectionSettings.ts +79 -0
  125. package/src/auth/{msalTokenCredential.ts → msal/msalTokenCredential.ts} +2 -1
  126. package/src/auth/msal/msalTokenProvider.ts +881 -0
  127. package/src/auth/settings.ts +378 -0
  128. package/src/auth/sidecar/sidecarAuthProvider.ts +234 -0
  129. package/src/auth/sidecar/sidecarConnectionSettings.ts +72 -0
  130. package/src/auth/sidecar/sidecarHttpClient.ts +443 -0
  131. package/src/auth/sidecar/sidecarModels.ts +153 -0
  132. package/src/auth/sidecar/sidecarTokenExpiry.ts +35 -0
  133. package/src/cloudAdapter.ts +292 -13
  134. package/src/connector-client/connectorClient.ts +116 -94
  135. package/src/errorHelper.ts +206 -7
  136. package/src/getProductInfo.ts +79 -1
  137. package/src/headerPropagation.ts +55 -23
  138. package/src/httpClient.ts +223 -0
  139. package/src/index.ts +6 -2
  140. package/src/oauth/userTokenClient.ts +137 -84
  141. package/src/observability/traces.ts +39 -8
  142. package/src/utils/env.ts +105 -0
  143. package/src/utils.ts +14 -0
  144. package/dist/src/auth/msalConnectionManager.d.ts +0 -64
  145. package/dist/src/auth/msalConnectionManager.js.map +0 -1
  146. package/dist/src/auth/msalTokenCredential.js.map +0 -1
  147. package/dist/src/auth/msalTokenProvider.js +0 -485
  148. package/dist/src/auth/msalTokenProvider.js.map +0 -1
  149. package/src/auth/msalTokenProvider.ts +0 -562
@@ -0,0 +1,881 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { AuthenticationResult, ConfidentialClientApplication, LogLevel, ManagedIdentityApplication, NodeSystemOptions } from '@azure/msal-node'
7
+ import { AuthConfiguration, AuthType, resolveAuthority, resolveAuthority as resolveAuthorityUtil, resolveAuthType } from '../authConfiguration'
8
+ import { AuthProvider } from '../authProvider'
9
+ import { debug, trace } from '@microsoft/agents-telemetry'
10
+ import { randomUUID } from 'crypto'
11
+ import { MemoryCache } from '../MemoryCache'
12
+ import jwt, { JwtPayload } from 'jsonwebtoken'
13
+ import fs from 'fs'
14
+ import crypto from 'crypto'
15
+ import { AuthenticationTraceDefinitions } from '../../observability'
16
+ import { ExceptionHelper } from '@microsoft/agents-activity'
17
+ import { Errors } from '../../errorHelper'
18
+
19
+ const audience = 'api://AzureADTokenExchange'
20
+ const logger = debug('agents:msal')
21
+ const agenticTokenRequestTimeoutMs = 30000
22
+
23
+ function isAbortError (error: unknown): error is Error {
24
+ return error instanceof Error && error.name === 'AbortError'
25
+ }
26
+
27
+ function createTokenRequestTimeoutError (timeoutMs: number): Error {
28
+ return ExceptionHelper.generateException(Error, Errors.TokenRequestTimeout, undefined, { timeoutMs: timeoutMs.toString() })
29
+ }
30
+
31
+ /**
32
+ * Provides tokens using MSAL.
33
+ */
34
+ export class MsalTokenProvider implements AuthProvider {
35
+ private static readonly _accessTokenCache = new MemoryCache<string>()
36
+ private static readonly _agenticTokenCache = new MemoryCache<string>()
37
+ private static readonly _confidentialClients = new Map<string, ConfidentialClientApplication>()
38
+ private static readonly _maxConfidentialClients = 100
39
+ public readonly connectionSettings?: AuthConfiguration
40
+
41
+ constructor (connectionSettings?: AuthConfiguration) {
42
+ this.connectionSettings = connectionSettings
43
+ }
44
+
45
+ /**
46
+ * Clears process-wide auth caches.
47
+ */
48
+ public static clearSharedCaches (): void {
49
+ MsalTokenProvider._accessTokenCache.clear()
50
+ MsalTokenProvider._agenticTokenCache.clear()
51
+ MsalTokenProvider._confidentialClients.clear()
52
+ }
53
+
54
+ private static cacheKey (...parts: Array<string | number | boolean | undefined | null>): string {
55
+ return JSON.stringify(parts.map(part => part ?? ''))
56
+ }
57
+
58
+ private static digest (value: string | Buffer | undefined): string {
59
+ return value ? crypto.createHash('sha256').update(value).digest('base64url') : ''
60
+ }
61
+
62
+ private getOrCreateConfidentialClient (
63
+ cacheKey: string,
64
+ createClient: () => ConfidentialClientApplication
65
+ ): ConfidentialClientApplication {
66
+ const existing = MsalTokenProvider._confidentialClients.get(cacheKey)
67
+ if (existing) {
68
+ MsalTokenProvider._confidentialClients.delete(cacheKey)
69
+ MsalTokenProvider._confidentialClients.set(cacheKey, existing)
70
+ return existing
71
+ }
72
+
73
+ const created = createClient()
74
+ MsalTokenProvider._confidentialClients.set(cacheKey, created)
75
+ while (MsalTokenProvider._confidentialClients.size > MsalTokenProvider._maxConfidentialClients) {
76
+ const oldestKey = MsalTokenProvider._confidentialClients.keys().next().value
77
+ if (oldestKey === undefined) {
78
+ break
79
+ }
80
+ MsalTokenProvider._confidentialClients.delete(oldestKey)
81
+ }
82
+ return created
83
+ }
84
+
85
+ private getFileCacheIdentity (path?: string): string {
86
+ if (!path) {
87
+ return ''
88
+ }
89
+
90
+ try {
91
+ const stat = fs.statSync(path)
92
+ return `${path}:${stat.size}:${stat.mtimeMs}`
93
+ } catch {
94
+ return path
95
+ }
96
+ }
97
+
98
+ private getAccessTokenCacheKey (authConfig: AuthConfiguration, scope: string): string {
99
+ const authType = resolveAuthType(authConfig)
100
+ let authority = resolveAuthorityUtil(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId)
101
+ let credentialIdentity = ''
102
+
103
+ switch (authType) {
104
+ case AuthType.ClientSecret:
105
+ credentialIdentity = MsalTokenProvider.digest(authConfig.clientSecret)
106
+ break
107
+ case AuthType.Certificate:
108
+ case AuthType.CertificateSubjectName:
109
+ credentialIdentity = MsalTokenProvider.cacheKey(
110
+ this.getFileCacheIdentity(authConfig.certPemFile),
111
+ this.getFileCacheIdentity(authConfig.certKeyFile),
112
+ authConfig.sendX5C
113
+ )
114
+ break
115
+ case AuthType.WorkloadIdentity:
116
+ authority = `https://login.microsoftonline.com/${authConfig.tenantId}`
117
+ credentialIdentity = this.getFileCacheIdentity(authConfig.federatedTokenFile ?? authConfig.WIDAssertionFile)
118
+ break
119
+ case AuthType.FederatedCredentials:
120
+ credentialIdentity = authConfig.federatedClientId ?? authConfig.FICClientId ?? ''
121
+ break
122
+ case AuthType.UserManagedIdentity:
123
+ authority = 'managed-identity'
124
+ credentialIdentity = authConfig.clientId ?? ''
125
+ break
126
+ case AuthType.SystemManagedIdentity:
127
+ authority = 'managed-identity'
128
+ credentialIdentity = 'system'
129
+ break
130
+ default:
131
+ credentialIdentity = 'unknown'
132
+ }
133
+
134
+ return MsalTokenProvider.cacheKey(
135
+ 'access-token',
136
+ authType,
137
+ authority,
138
+ authConfig.clientId,
139
+ scope,
140
+ authConfig.azureRegion,
141
+ credentialIdentity
142
+ )
143
+ }
144
+
145
+ private cacheAccessToken (cacheKey: string, token: string, expiresOn?: Date | null): void {
146
+ const ttlSeconds = this.getTokenCacheTtlSeconds(token, expiresOn)
147
+ if (ttlSeconds > 0) {
148
+ MsalTokenProvider._accessTokenCache.set(cacheKey, token, ttlSeconds)
149
+ }
150
+ }
151
+
152
+ private cacheAgenticToken (cacheKey: string, token: string, ttlSeconds: number): void {
153
+ const safeTtlSeconds = Math.floor(ttlSeconds) - 300
154
+ if (safeTtlSeconds > 0) {
155
+ MsalTokenProvider._agenticTokenCache.set(cacheKey, token, safeTtlSeconds)
156
+ }
157
+ }
158
+
159
+ private cacheAgenticAuthenticationResult (cacheKey: string, token: AuthenticationResult): void {
160
+ const ttlSeconds = this.getTokenCacheTtlSeconds(token.accessToken, token.expiresOn)
161
+ if (ttlSeconds > 0) {
162
+ MsalTokenProvider._agenticTokenCache.set(cacheKey, token.accessToken, ttlSeconds)
163
+ }
164
+ }
165
+
166
+ private getAgenticTokenCacheKey (
167
+ tenantId: string,
168
+ clientId: string,
169
+ scopes: string[],
170
+ tokenBodyParameters: { [key: string]: any }
171
+ ): string {
172
+ const bodyKey = Object.keys(tokenBodyParameters)
173
+ .sort()
174
+ .filter(key => key !== 'user_federated_identity_credential')
175
+ .map(key => `${key}=${MsalTokenProvider.digest(String(tokenBodyParameters[key]))}`)
176
+ .join('&')
177
+
178
+ return MsalTokenProvider.cacheKey(
179
+ 'agentic-token',
180
+ this.resolveAuthority(tenantId),
181
+ clientId,
182
+ scopes.join(' '),
183
+ bodyKey
184
+ )
185
+ }
186
+
187
+ private getTokenCacheTtlSeconds (token: string, expiresOn?: Date | null): number {
188
+ const expiresAtMs = expiresOn?.getTime() ?? this.getJwtExpiresAtMs(token)
189
+ if (!expiresAtMs) {
190
+ return 0
191
+ }
192
+
193
+ return Math.floor((expiresAtMs - Date.now()) / 1000) - 300
194
+ }
195
+
196
+ private getJwtExpiresAtMs (token: string): number | undefined {
197
+ const payload = jwt.decode(token) as JwtPayload | string | null
198
+ if (!payload || typeof payload === 'string' || typeof payload.exp !== 'number') {
199
+ return undefined
200
+ }
201
+
202
+ return payload.exp * 1000
203
+ }
204
+
205
+ private getClientSecretClient (authConfig: AuthConfiguration): ConfidentialClientApplication {
206
+ const cacheKey = MsalTokenProvider.cacheKey(
207
+ 'confidential-client',
208
+ AuthType.ClientSecret,
209
+ authConfig.clientId,
210
+ resolveAuthorityUtil(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId),
211
+ MsalTokenProvider.digest(authConfig.clientSecret)
212
+ )
213
+
214
+ return this.getOrCreateConfidentialClient(cacheKey, () => new ConfidentialClientApplication({
215
+ auth: {
216
+ clientId: authConfig.clientId as string,
217
+ authority: resolveAuthorityUtil(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId),
218
+ clientSecret: authConfig.clientSecret
219
+ },
220
+ system: this.sysOptions
221
+ }))
222
+ }
223
+
224
+ private getCertificateClient (authConfig: AuthConfiguration): ConfidentialClientApplication {
225
+ const cacheKey = MsalTokenProvider.cacheKey(
226
+ 'confidential-client',
227
+ authConfig.authType ?? AuthType.Certificate,
228
+ authConfig.clientId,
229
+ resolveAuthorityUtil(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId),
230
+ this.getFileCacheIdentity(authConfig.certPemFile),
231
+ this.getFileCacheIdentity(authConfig.certKeyFile),
232
+ authConfig.sendX5C
233
+ )
234
+
235
+ return this.getOrCreateConfidentialClient(cacheKey, () => {
236
+ const privateKeySource = fs.readFileSync(authConfig.certKeyFile as string)
237
+
238
+ const privateKeyObject = crypto.createPrivateKey({
239
+ key: privateKeySource,
240
+ format: 'pem'
241
+ })
242
+
243
+ const privateKey = privateKeyObject.export({
244
+ format: 'pem',
245
+ type: 'pkcs8'
246
+ })
247
+
248
+ const pemFile = fs.readFileSync(authConfig.certPemFile as string)
249
+ const pubKeyObject = new crypto.X509Certificate(pemFile)
250
+
251
+ return new ConfidentialClientApplication({
252
+ auth: {
253
+ clientId: authConfig.clientId || '',
254
+ authority: resolveAuthority(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId),
255
+ clientCertificate: {
256
+ privateKey: privateKey as string,
257
+ thumbprint: pubKeyObject.fingerprint.replaceAll(':', ''),
258
+ x5c: pemFile.toString()
259
+ }
260
+ },
261
+ system: this.sysOptions
262
+ })
263
+ })
264
+ }
265
+
266
+ /**
267
+ * Gets an access token using the auth configuration from the MsalTokenProvider instance and the provided scope.
268
+ * @param scope The scope for the token.
269
+ * @returns A promise that resolves to the access token.
270
+ */
271
+ public async getAccessToken (scope: string): Promise<string>
272
+ /**
273
+ * Gets an access token.
274
+ * @param authConfig The authentication configuration.
275
+ * @param scope The scope for the token.
276
+ * @returns A promise that resolves to the access token.
277
+ */
278
+ public async getAccessToken (authConfig: AuthConfiguration, scope: string): Promise<string>
279
+ public async getAccessToken (authConfigOrScope: AuthConfiguration | string, scope?: string): Promise<string> {
280
+ return trace(AuthenticationTraceDefinitions.getAccessToken, async ({ record }) => {
281
+ let authConfig: AuthConfiguration
282
+ let actualScope: string
283
+
284
+ if (typeof authConfigOrScope === 'string') {
285
+ // Called as getAccessToken(scope)
286
+ if (!this.connectionSettings) {
287
+ throw new Error('Connection settings must be provided to constructor when calling getAccessToken(scope)')
288
+ }
289
+ authConfig = this.connectionSettings
290
+ actualScope = authConfigOrScope
291
+ } else {
292
+ // Called as getAccessToken(authConfig, scope)
293
+ authConfig = authConfigOrScope
294
+ actualScope = scope as string
295
+ }
296
+
297
+ record({ scope: actualScope })
298
+
299
+ if (!authConfig.clientId && process.env.NODE_ENV !== 'production') {
300
+ record({ method: 'unknown' })
301
+ return ''
302
+ }
303
+
304
+ const accessTokenCacheKey = this.getAccessTokenCacheKey(authConfig, actualScope)
305
+ const cachedAccessToken = MsalTokenProvider._accessTokenCache.get(accessTokenCacheKey)
306
+ if (cachedAccessToken) {
307
+ logger.debug('getAccessToken cache hit clientId=%s scope=%s', authConfig.clientId, actualScope)
308
+ return cachedAccessToken
309
+ }
310
+
311
+ let token
312
+ const authType = resolveAuthType(authConfig)
313
+ record({ method: authType })
314
+ logger.debug('getAccessToken via method=%s clientId=%s scope=%s', authType, authConfig.clientId, actualScope)
315
+
316
+ switch (authType) {
317
+ case AuthType.WorkloadIdentity: {
318
+ const tokenFilePath = authConfig.federatedTokenFile ?? authConfig.WIDAssertionFile
319
+ if (!tokenFilePath) {
320
+ throw ExceptionHelper.generateException(Error, Errors.WorkloadIdentityTokenFileRequired)
321
+ }
322
+ token = await this.acquireAccessTokenViaWID(authConfig, actualScope)
323
+ break
324
+ }
325
+ case AuthType.FederatedCredentials:
326
+ if (!authConfig.federatedClientId && !authConfig.FICClientId) {
327
+ throw ExceptionHelper.generateException(Error, Errors.FICClientIdRequired)
328
+ }
329
+ token = await this.acquireAccessTokenViaFIC(authConfig, actualScope)
330
+ break
331
+ case AuthType.ClientSecret:
332
+ if (!authConfig.clientSecret) {
333
+ throw ExceptionHelper.generateException(Error, Errors.ClientSecretRequired)
334
+ }
335
+ token = await this.acquireAccessTokenViaSecret(authConfig, actualScope)
336
+ break
337
+ case AuthType.Certificate:
338
+ case AuthType.CertificateSubjectName:
339
+ if (!authConfig.certPemFile || !authConfig.certKeyFile) {
340
+ throw ExceptionHelper.generateException(Error, Errors.CertificateFilesRequired)
341
+ }
342
+ token = await this.acquireTokenWithCertificate(authConfig, actualScope)
343
+ break
344
+ case AuthType.UserManagedIdentity:
345
+ if (!authConfig.clientId) {
346
+ throw ExceptionHelper.generateException(Error, Errors.ClientIdRequiredForUserManagedIdentity)
347
+ }
348
+ token = await this.acquireTokenWithUserAssignedIdentity(authConfig, actualScope)
349
+ break
350
+ case AuthType.SystemManagedIdentity:
351
+ token = await this.acquireTokenWithSystemAssignedIdentity(authConfig, actualScope)
352
+ break
353
+ default:
354
+ throw ExceptionHelper.generateException(Error, Errors.UnsupportedAuthType, undefined, { authType })
355
+ }
356
+
357
+ if (token === undefined) {
358
+ throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireToken)
359
+ }
360
+
361
+ this.cacheAccessToken(accessTokenCacheKey, token)
362
+ return token
363
+ })
364
+ }
365
+
366
+ public async acquireTokenOnBehalfOf (scopes: string[], oboAssertion: string): Promise<string>
367
+ public async acquireTokenOnBehalfOf (authConfig: AuthConfiguration, scopes: string[], oboAssertion: string): Promise<string>
368
+ public async acquireTokenOnBehalfOf (
369
+ authConfigOrScopes: AuthConfiguration | string[],
370
+ scopesOrOboAssertion?: string[] | string,
371
+ oboAssertion?: string
372
+ ): Promise<string> {
373
+ return trace(AuthenticationTraceDefinitions.acquireTokenOnBehalfOf, async ({ record }) => {
374
+ let authConfig: AuthConfiguration
375
+ let actualScopes: string[]
376
+ let actualOboAssertion: string
377
+
378
+ if (Array.isArray(authConfigOrScopes)) {
379
+ // Called as acquireTokenOnBehalfOf(scopes, oboAssertion)
380
+ if (!this.connectionSettings) {
381
+ throw new Error('Connection settings must be provided to constructor when calling acquireTokenOnBehalfOf(scopes, oboAssertion)')
382
+ }
383
+ authConfig = this.connectionSettings
384
+ actualScopes = authConfigOrScopes
385
+ actualOboAssertion = scopesOrOboAssertion as string
386
+ } else {
387
+ // Called as acquireTokenOnBehalfOf(authConfig, scopes, oboAssertion)
388
+ authConfig = authConfigOrScopes
389
+ actualScopes = scopesOrOboAssertion as string[]
390
+ actualOboAssertion = oboAssertion!
391
+ }
392
+
393
+ record({ scopes: actualScopes })
394
+ logger.debug('acquireTokenOnBehalfOf clientId=%s scopes=%o', authConfig.clientId, actualScopes)
395
+
396
+ const cca = this.getClientSecretClient(authConfig)
397
+ const token = await cca.acquireTokenOnBehalfOf({
398
+ oboAssertion: actualOboAssertion,
399
+ scopes: actualScopes
400
+ })
401
+ if (!token?.accessToken) {
402
+ throw new Error('Failed to acquire token on behalf of user')
403
+ }
404
+
405
+ return token.accessToken
406
+ })
407
+ }
408
+
409
+ public async getAgenticInstanceToken (tenantId: string, agentAppInstanceId: string): Promise<string> {
410
+ return trace(AuthenticationTraceDefinitions.getAgenticInstanceToken, async ({ record }) => {
411
+ logger.debug('getAgenticInstanceToken tenantId=%s agentAppInstanceId=%s', tenantId, agentAppInstanceId)
412
+ record({ agenticInstanceId: agentAppInstanceId })
413
+
414
+ if (!this.connectionSettings) {
415
+ throw new Error('Connection settings must be provided when calling getAgenticInstanceToken')
416
+ }
417
+
418
+ const instanceTokenCacheKey = MsalTokenProvider.cacheKey(
419
+ 'agentic-instance-token',
420
+ this.resolveAuthority(tenantId),
421
+ agentAppInstanceId,
422
+ this.connectionSettings.clientId,
423
+ this.connectionSettings.azureRegion
424
+ )
425
+ const cachedInstanceToken = MsalTokenProvider._agenticTokenCache.get(instanceTokenCacheKey)
426
+ if (cachedInstanceToken) {
427
+ return cachedInstanceToken
428
+ }
429
+
430
+ const appToken = await this.getAgenticApplicationToken(tenantId, agentAppInstanceId)
431
+ const cca = new ConfidentialClientApplication({
432
+ auth: {
433
+ clientId: agentAppInstanceId,
434
+ clientAssertion: appToken,
435
+ authority: this.resolveAuthority(tenantId),
436
+ },
437
+ system: this.sysOptions
438
+ })
439
+
440
+ const token = await cca.acquireTokenByClientCredential({
441
+ scopes: ['api://AzureAdTokenExchange/.default'],
442
+ correlationId: randomUUID(),
443
+ azureRegion: this.connectionSettings?.azureRegion
444
+ })
445
+
446
+ if (!token?.accessToken) {
447
+ throw new Error(`Failed to acquire instance token for agent instance: ${agentAppInstanceId}`)
448
+ }
449
+
450
+ this.cacheAgenticAuthenticationResult(instanceTokenCacheKey, token)
451
+ return token.accessToken
452
+ })
453
+ }
454
+
455
+ /**
456
+ * This method can optionally accept a tenant ID that overrides the tenant ID in the connection settings.
457
+ * The passed tenantId is always preferred over the configured tenantId when present.
458
+ * @param tenantId
459
+ * @returns
460
+ */
461
+ private resolveAuthority (tenantId?: string) : string {
462
+ const { authorityEndpoint: configuredAuth, authority, tenantId: configuredTenantId } = this.connectionSettings ?? {}
463
+
464
+ if (!tenantId) {
465
+ // No agentic tenant override — delegate to shared utility
466
+ return resolveAuthorityUtil(configuredAuth ?? authority, configuredTenantId)
467
+ }
468
+
469
+ // Agentic override: build a clean base using the override tenant, then replace any
470
+ // /common or GUID placeholder left in the authority (e.g. from a multi-tenant config)
471
+ const base = resolveAuthorityUtil(configuredAuth ?? authority, tenantId)
472
+ const guidPattern = /\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/
473
+
474
+ if (base.endsWith('/common') || guidPattern.test(base)) {
475
+ return base.replace(
476
+ /\/(?:common|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})(?=\/|$)/,
477
+ `/${tenantId}`
478
+ )
479
+ }
480
+
481
+ return base
482
+ }
483
+
484
+ /**
485
+ * Does a direct HTTP call to acquire a token for agentic scenarios - do not use this directly!
486
+ * This method will be removed once MSAL is updated with the necessary features.
487
+ * (This is required in order to pass additional parameters into the auth call)
488
+ * @param tenantId
489
+ * @param clientId
490
+ * @param clientAssertion
491
+ * @param scopes
492
+ * @param tokenBodyParameters
493
+ * @returns
494
+ */
495
+ private async acquireTokenForAgenticScenarios (tenantId: string, clientId: string, clientAssertion: string | undefined, scopes: string[], tokenBodyParameters: { [key: string]: any }): Promise<string | null> {
496
+ if (!this.connectionSettings) {
497
+ throw new Error('Connection settings must be provided when calling getAgenticInstanceToken')
498
+ }
499
+
500
+ logger.debug('acquireTokenForAgenticScenarios clientId=%s tenantId=%s scopes=%o grant_type=%s', clientId, tenantId, scopes, tokenBodyParameters.grant_type)
501
+ // Check cache first
502
+ const cacheKey = this.getAgenticTokenCacheKey(tenantId, clientId, scopes, tokenBodyParameters)
503
+ if (MsalTokenProvider._agenticTokenCache.get(cacheKey)) {
504
+ return MsalTokenProvider._agenticTokenCache.get(cacheKey) as string
505
+ }
506
+
507
+ const url = `${this.resolveAuthority(tenantId)}/oauth2/v2.0/token`
508
+
509
+ const data: { [key: string]: any } = {
510
+ client_id: clientId,
511
+ scope: scopes.join(' '),
512
+ ...tokenBodyParameters
513
+ }
514
+
515
+ if (clientAssertion) {
516
+ data.client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
517
+ data.client_assertion = clientAssertion
518
+ } else {
519
+ data.client_secret = this.connectionSettings.clientSecret
520
+ }
521
+
522
+ if (data.grant_type !== 'user_fic') {
523
+ data.client_info = '2'
524
+ }
525
+
526
+ const controller = new AbortController()
527
+ const timeoutId = setTimeout(() => {
528
+ controller.abort(createTokenRequestTimeoutError(agenticTokenRequestTimeoutMs))
529
+ }, agenticTokenRequestTimeoutMs)
530
+
531
+ const token = await fetch(
532
+ url,
533
+ {
534
+ method: 'POST',
535
+ headers: {
536
+ 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
537
+ },
538
+ body: new URLSearchParams(data as Record<string, string>).toString(),
539
+ signal: controller.signal
540
+ }
541
+ ).then(async (response) => {
542
+ if (!response.ok) {
543
+ const errorBody = await response.text()
544
+ const error = new Error(`Token request failed with status ${response.status}: ${errorBody}`)
545
+ ;(error as any).toJSON = () => ({ status: response.status, body: errorBody })
546
+ throw error
547
+ }
548
+ return response.json() as Promise<{ access_token: string, expires_in: number }>
549
+ }).catch((error) => {
550
+ const resolvedError = isAbortError(error)
551
+ ? (controller.signal.reason instanceof Error ? controller.signal.reason : createTokenRequestTimeoutError(agenticTokenRequestTimeoutMs))
552
+ : error
553
+ logger.error('Error acquiring token: ', resolvedError.toJSON ? resolvedError.toJSON() : resolvedError)
554
+ throw resolvedError
555
+ }).finally(() => {
556
+ clearTimeout(timeoutId)
557
+ })
558
+
559
+ // capture token, expire local cache 5 minutes early
560
+ this.cacheAgenticToken(cacheKey, token.access_token, token.expires_in)
561
+ return token.access_token
562
+ }
563
+
564
+ public async getAgenticUserToken (tenantId: string, agentAppInstanceId: string, agenticUserId: string, scopes: string[]): Promise<string> {
565
+ return trace(AuthenticationTraceDefinitions.getAgenticUserToken, async ({ record }) => {
566
+ logger.debug('getAgenticUserToken tenantId=%s agentAppInstanceId=%s scopes=%o', tenantId, agentAppInstanceId, scopes)
567
+ record({ agenticInstanceId: agentAppInstanceId, agenticUserId, scopes })
568
+
569
+ const userTokenParameters = {
570
+ user_id: agenticUserId,
571
+ grant_type: 'user_fic',
572
+ }
573
+ const userTokenCacheKey = this.getAgenticTokenCacheKey(tenantId, agentAppInstanceId, scopes, userTokenParameters)
574
+ const cachedUserToken = MsalTokenProvider._agenticTokenCache.get(userTokenCacheKey)
575
+ if (cachedUserToken) {
576
+ return cachedUserToken
577
+ }
578
+
579
+ const agentToken = await this.getAgenticApplicationToken(tenantId, agentAppInstanceId)
580
+ const instanceToken = await this.getAgenticInstanceToken(tenantId, agentAppInstanceId)
581
+
582
+ const token = await this.acquireTokenForAgenticScenarios(tenantId, agentAppInstanceId, agentToken, scopes, {
583
+ ...userTokenParameters,
584
+ user_federated_identity_credential: instanceToken,
585
+ })
586
+
587
+ if (!token) {
588
+ throw new Error(`Failed to acquire instance token for user token: ${agentAppInstanceId}`)
589
+ }
590
+
591
+ return token
592
+ })
593
+ }
594
+
595
+ public async getAgenticApplicationToken (tenantId: string, agentAppInstanceId: string): Promise<string> {
596
+ if (!this.connectionSettings?.clientId) {
597
+ throw new Error('Connection settings must be provided when calling getAgenticApplicationToken')
598
+ }
599
+ logger.debug('getAgenticApplicationToken clientId=%s tenantId=%s agentAppInstanceId=%s', this.connectionSettings.clientId, tenantId, agentAppInstanceId)
600
+
601
+ const authType = resolveAuthType(this.connectionSettings)
602
+
603
+ if (authType === AuthType.IdentityProxyManager) {
604
+ let resource: string
605
+ if (!this.connectionSettings.idpmResource) {
606
+ resource = 'api://AzureAdTokenExchange/.default'
607
+ } else if (!URL.canParse(this.connectionSettings.idpmResource)) {
608
+ throw new Error('idpmResource must be a valid absolute URI')
609
+ } else {
610
+ resource = this.connectionSettings.idpmResource
611
+ }
612
+ const msiApp = new ManagedIdentityApplication({
613
+ managedIdentityIdParams: {
614
+ userAssignedClientId: this.connectionSettings.clientId
615
+ },
616
+ system: this.sysOptions
617
+ })
618
+ const tokenResult = await msiApp.acquireToken({ resource })
619
+ if (!tokenResult?.accessToken) {
620
+ throw new Error(`Failed to acquire token via IdentityProxyManager for agent instance: ${agentAppInstanceId}`)
621
+ }
622
+ logger.debug('getAgenticApplicationToken via IdentityProxyManager clientId=%s resource=%s', this.connectionSettings.clientId, resource)
623
+ return tokenResult.accessToken
624
+ }
625
+
626
+ let clientAssertion
627
+
628
+ switch (authType) {
629
+ case AuthType.WorkloadIdentity: {
630
+ const tokenFilePath = this.connectionSettings.federatedTokenFile ?? this.connectionSettings.WIDAssertionFile
631
+ if (tokenFilePath === undefined) {
632
+ throw ExceptionHelper.generateException(Error, Errors.WorkloadIdentityTokenFileRequired)
633
+ }
634
+ clientAssertion = fs.readFileSync(tokenFilePath as string, 'utf8')
635
+ break
636
+ }
637
+ case AuthType.FederatedCredentials:
638
+ if (!this.connectionSettings.federatedClientId && !this.connectionSettings.FICClientId) {
639
+ throw ExceptionHelper.generateException(Error, Errors.FICClientIdRequired)
640
+ }
641
+ clientAssertion = await this.fetchExternalToken(this.connectionSettings.federatedClientId as string || this.connectionSettings.FICClientId as string)
642
+ break
643
+ case AuthType.Certificate:
644
+ case AuthType.CertificateSubjectName:
645
+ if (!this.connectionSettings.certPemFile || !this.connectionSettings.certKeyFile) {
646
+ throw ExceptionHelper.generateException(Error, Errors.CertificateFilesRequired)
647
+ }
648
+ clientAssertion = this.getAssertionFromCert(this.connectionSettings)
649
+ break
650
+ }
651
+
652
+ const token = await this.acquireTokenForAgenticScenarios(tenantId, this.connectionSettings.clientId, clientAssertion, ['api://AzureAdTokenExchange/.default'], {
653
+ grant_type: 'client_credentials',
654
+ fmi_path: agentAppInstanceId,
655
+ })
656
+
657
+ if (!token) {
658
+ throw new Error(`Failed to acquire token for agent instance: ${agentAppInstanceId}`)
659
+ }
660
+
661
+ return token
662
+ }
663
+
664
+ private readonly sysOptions: NodeSystemOptions = {
665
+ loggerOptions: {
666
+ logLevel: LogLevel.Trace,
667
+ loggerCallback: (level, message, containsPii) => {
668
+ if (containsPii) {
669
+ return
670
+ }
671
+ switch (level) {
672
+ case LogLevel.Error:
673
+ logger.error(message)
674
+ return
675
+ case LogLevel.Info:
676
+ logger.debug(message)
677
+ return
678
+ case LogLevel.Warning:
679
+ if (!message.includes('Warning - No client info in response')) {
680
+ logger.warn(message)
681
+ }
682
+ return
683
+ case LogLevel.Verbose:
684
+ logger.debug(message)
685
+ }
686
+ },
687
+ piiLoggingEnabled: false
688
+ }
689
+ }
690
+
691
+ /**
692
+ * Generates the client assertion using the provided certificate.
693
+ * @param authConfig The authentication configuration.
694
+ * @returns The client assertion.
695
+ */
696
+ private getAssertionFromCert (authConfig: AuthConfiguration): string {
697
+ const base64url = (buf: Buffer) =>
698
+ buf.toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
699
+
700
+ const privateKeyPem = fs.readFileSync(authConfig.certKeyFile as string)
701
+
702
+ const pemFile = fs.readFileSync(authConfig.certPemFile as string)
703
+ const pubKeyObject = new crypto.X509Certificate(pemFile)
704
+
705
+ const der = pubKeyObject.raw
706
+ const x5tS256 = base64url(crypto.createHash('sha256').update(der).digest())
707
+
708
+ let x5c
709
+ if (authConfig.sendX5C) {
710
+ x5c = pemFile.toString()
711
+ }
712
+
713
+ const now = Math.floor(Date.now() / 1000)
714
+ const payload = {
715
+ aud: `${this.resolveAuthority(authConfig.tenantId)}/oauth2/v2.0/token`,
716
+ iss: authConfig.clientId,
717
+ sub: authConfig.clientId,
718
+ jti: randomUUID(),
719
+ nbf: now,
720
+ iat: now,
721
+ exp: now + 600, // 10 minutes
722
+ }
723
+
724
+ return jwt.sign(
725
+ payload,
726
+ privateKeyPem,
727
+ {
728
+ algorithm: 'PS256',
729
+ header: { alg: 'PS256', typ: 'JWT', 'x5t#S256': x5tS256, x5c }
730
+ }
731
+ )
732
+ }
733
+
734
+ /**
735
+ * Acquires a token using a user-assigned identity.
736
+ * @param authConfig The authentication configuration.
737
+ * @param scope The scope for the token.
738
+ * @returns A promise that resolves to the access token.
739
+ */
740
+ private async acquireTokenWithUserAssignedIdentity (authConfig: AuthConfiguration, scope: string) {
741
+ const mia = new ManagedIdentityApplication({
742
+ managedIdentityIdParams: {
743
+ userAssignedClientId: authConfig.clientId || ''
744
+ },
745
+ system: this.sysOptions
746
+ })
747
+ const token = await mia.acquireToken({
748
+ resource: scope
749
+ })
750
+ return token?.accessToken
751
+ }
752
+
753
+ /**
754
+ * Acquires a token using a system-assigned identity.
755
+ * @param authConfig The authentication configuration.
756
+ * @param scope The scope for the token.
757
+ * @returns A promise that resolves to the access token.
758
+ */
759
+ private async acquireTokenWithSystemAssignedIdentity (authConfig: AuthConfiguration, scope: string) {
760
+ const mia = new ManagedIdentityApplication({
761
+ system: this.sysOptions
762
+ })
763
+ const token = await mia.acquireToken({
764
+ resource: scope
765
+ })
766
+ return token?.accessToken
767
+ }
768
+
769
+ /**
770
+ * Acquires a token using a certificate.
771
+ * @param authConfig The authentication configuration.
772
+ * @param scope The scope for the token.
773
+ * @returns A promise that resolves to the access token.
774
+ */
775
+ private async acquireTokenWithCertificate (authConfig: AuthConfiguration, scope: string) {
776
+ const cca = this.getCertificateClient(authConfig)
777
+ const token = await cca.acquireTokenByClientCredential({
778
+ scopes: [`${scope}/.default`],
779
+ correlationId: randomUUID(),
780
+ azureRegion: authConfig.azureRegion
781
+ })
782
+ if (!token?.accessToken) {
783
+ throw new Error('Failed to acquire token using certificate')
784
+ }
785
+ return token.accessToken
786
+ }
787
+
788
+ /**
789
+ * Acquires a token using a client secret.
790
+ * @param authConfig The authentication configuration.
791
+ * @param scope The scope for the token.
792
+ * @returns A promise that resolves to the access token.
793
+ */
794
+ private async acquireAccessTokenViaSecret (authConfig: AuthConfiguration, scope: string) {
795
+ const cca = this.getClientSecretClient(authConfig)
796
+ const token = await cca.acquireTokenByClientCredential({
797
+ scopes: [`${scope}/.default`],
798
+ correlationId: randomUUID(),
799
+ azureRegion: authConfig.azureRegion
800
+ })
801
+ if (!token?.accessToken) {
802
+ throw new Error('Failed to acquire token using client secret')
803
+ }
804
+ return token.accessToken
805
+ }
806
+
807
+ /**
808
+ * Acquires a token using a FIC client assertion.
809
+ * @param authConfig The authentication configuration.
810
+ * @param scope The scope for the token.
811
+ * @returns A promise that resolves to the access token.
812
+ */
813
+ private async acquireAccessTokenViaFIC (authConfig: AuthConfiguration, scope: string) : Promise<string> {
814
+ const scopes = [`${scope}/.default`]
815
+ const clientAssertion = await this.fetchExternalToken(authConfig.federatedClientId as string || authConfig.FICClientId as string)
816
+ const cca = new ConfidentialClientApplication({
817
+ auth: {
818
+ clientId: authConfig.clientId as string,
819
+ authority: `${authConfig.authorityEndpoint ?? authConfig.authority}/${authConfig.tenantId}`,
820
+ clientAssertion
821
+ },
822
+ system: this.sysOptions
823
+ })
824
+ const token = await cca.acquireTokenByClientCredential({ scopes, azureRegion: authConfig.azureRegion })
825
+ logger.debug('got token using FIC client assertion')
826
+ if (!token?.accessToken) {
827
+ throw new Error('Failed to acquire token using FIC client assertion')
828
+ }
829
+ return token.accessToken
830
+ }
831
+
832
+ /**
833
+ * Acquires a token using a Workload Identity client assertion.
834
+ * @param authConfig The authentication configuration.
835
+ * @param scope The scope for the token.
836
+ * @returns A promise that resolves to the access token.
837
+ */
838
+ private async acquireAccessTokenViaWID (authConfig: AuthConfiguration, scope: string) : Promise<string> {
839
+ const scopes = [`${scope}/.default`]
840
+ const tokenFilePath = authConfig.federatedTokenFile ?? authConfig.WIDAssertionFile
841
+ const clientAssertion = fs.readFileSync(tokenFilePath as string, 'utf8')
842
+ const cca = new ConfidentialClientApplication({
843
+ auth: {
844
+ clientId: authConfig.clientId as string,
845
+ authority: `https://login.microsoftonline.com/${authConfig.tenantId}`,
846
+ clientAssertion
847
+ },
848
+ system: this.sysOptions
849
+ })
850
+ const token = await cca.acquireTokenByClientCredential({ scopes, azureRegion: authConfig.azureRegion })
851
+ logger.debug('got token using WID client assertion')
852
+ if (!token?.accessToken) {
853
+ throw new Error('Failed to acquire token using WID client assertion')
854
+ }
855
+ return token.accessToken
856
+ }
857
+
858
+ /**
859
+ * Fetches an external token.
860
+ * @param FICClientId The FIC client ID.
861
+ * @returns A promise that resolves to the external token.
862
+ */
863
+ private async fetchExternalToken (FICClientId: string) : Promise<string> {
864
+ const managedIdentityClientAssertion = new ManagedIdentityApplication({
865
+ managedIdentityIdParams: {
866
+ userAssignedClientId: FICClientId
867
+ },
868
+ system: this.sysOptions
869
+ }
870
+ )
871
+ const response = await managedIdentityClientAssertion.acquireToken({
872
+ resource: audience,
873
+ forceRefresh: true
874
+ })
875
+ logger.debug('got token for FIC')
876
+ if (!response?.accessToken) {
877
+ throw new Error('Failed to acquire external token for FIC client assertion')
878
+ }
879
+ return response.accessToken
880
+ }
881
+ }