@azure/identity-cache-persistence 1.1.1 → 1.1.2-alpha.20240619.4

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 (20) hide show
  1. package/dist-esm/identity/src/constants.js +1 -1
  2. package/dist-esm/identity/src/constants.js.map +1 -1
  3. package/dist-esm/identity/src/credentials/azurePipelinesCredential.js +35 -60
  4. package/dist-esm/identity/src/credentials/azurePipelinesCredential.js.map +1 -1
  5. package/dist-esm/identity/src/credentials/interactiveBrowserCredential.js +18 -21
  6. package/dist-esm/identity/src/credentials/interactiveBrowserCredential.js.map +1 -1
  7. package/dist-esm/identity/src/credentials/managedIdentityCredential/index.js +3 -288
  8. package/dist-esm/identity/src/credentials/managedIdentityCredential/index.js.map +1 -1
  9. package/dist-esm/identity/src/credentials/managedIdentityCredential/legacyMsiProvider.js +309 -0
  10. package/dist-esm/identity/src/credentials/managedIdentityCredential/legacyMsiProvider.js.map +1 -0
  11. package/dist-esm/identity/src/credentials/onBehalfOfCredential.js +66 -6
  12. package/dist-esm/identity/src/credentials/onBehalfOfCredential.js.map +1 -1
  13. package/dist-esm/identity/src/msal/nodeFlows/msalClient.js +135 -24
  14. package/dist-esm/identity/src/msal/nodeFlows/msalClient.js.map +1 -1
  15. package/dist-esm/identity/src/msal/nodeFlows/msalPlugins.js.map +1 -1
  16. package/package.json +3 -3
  17. package/dist-esm/identity/src/msal/nodeFlows/msalClientCertificate.js +0 -122
  18. package/dist-esm/identity/src/msal/nodeFlows/msalClientCertificate.js.map +0 -1
  19. package/dist-esm/identity/src/msal/nodeFlows/msalOnBehalfOf.js +0 -66
  20. package/dist-esm/identity/src/msal/nodeFlows/msalOnBehalfOf.js.map +0 -1
@@ -0,0 +1,309 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { __awaiter } from "tslib";
4
+ import { ConfidentialClientApplication } from "@azure/msal-node";
5
+ import { AuthenticationError, AuthenticationRequiredError, CredentialUnavailableError, } from "../../errors";
6
+ import { cloudShellMsi } from "./cloudShellMsi";
7
+ import { credentialLogger, formatError, formatSuccess } from "../../util/logging";
8
+ import { DeveloperSignOnClientId } from "../../constants";
9
+ import { IdentityClient } from "../../client/identityClient";
10
+ import { appServiceMsi2017 } from "./appServiceMsi2017";
11
+ import { appServiceMsi2019 } from "./appServiceMsi2019";
12
+ import { arcMsi } from "./arcMsi";
13
+ import { fabricMsi } from "./fabricMsi";
14
+ import { getLogLevel } from "@azure/logger";
15
+ import { getMSALLogLevel } from "../../msal/utils";
16
+ import { imdsMsi } from "./imdsMsi";
17
+ import { tokenExchangeMsi } from "./tokenExchangeMsi";
18
+ import { tracingClient } from "../../util/tracing";
19
+ const logger = credentialLogger("ManagedIdentityCredential");
20
+ export class LegacyMsiProvider {
21
+ constructor(clientIdOrOptions, options) {
22
+ var _a, _b;
23
+ this.isEndpointUnavailable = null;
24
+ this.isAppTokenProviderInitialized = false;
25
+ this.msiRetryConfig = {
26
+ maxRetries: 5,
27
+ startDelayInMs: 800,
28
+ intervalIncrement: 2,
29
+ };
30
+ let _options;
31
+ if (typeof clientIdOrOptions === "string") {
32
+ this.clientId = clientIdOrOptions;
33
+ _options = options;
34
+ }
35
+ else {
36
+ this.clientId = clientIdOrOptions === null || clientIdOrOptions === void 0 ? void 0 : clientIdOrOptions.clientId;
37
+ _options = clientIdOrOptions;
38
+ }
39
+ this.resourceId = _options === null || _options === void 0 ? void 0 : _options.resourceId;
40
+ // For JavaScript users.
41
+ if (this.clientId && this.resourceId) {
42
+ throw new Error(`ManagedIdentityCredential - Client Id and Resource Id can't be provided at the same time.`);
43
+ }
44
+ if (((_a = _options === null || _options === void 0 ? void 0 : _options.retryOptions) === null || _a === void 0 ? void 0 : _a.maxRetries) !== undefined) {
45
+ this.msiRetryConfig.maxRetries = _options.retryOptions.maxRetries;
46
+ }
47
+ this.identityClient = new IdentityClient(_options);
48
+ this.isAvailableIdentityClient = new IdentityClient(Object.assign(Object.assign({}, _options), { retryOptions: {
49
+ maxRetries: 0,
50
+ } }));
51
+ /** authority host validation and metadata discovery to be skipped in managed identity
52
+ * since this wasn't done previously before adding token cache support
53
+ */
54
+ this.confidentialApp = new ConfidentialClientApplication({
55
+ auth: {
56
+ authority: "https://login.microsoftonline.com/managed_identity",
57
+ clientId: (_b = this.clientId) !== null && _b !== void 0 ? _b : DeveloperSignOnClientId,
58
+ clientSecret: "dummy-secret",
59
+ cloudDiscoveryMetadata: '{"tenant_discovery_endpoint":"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration","api-version":"1.1","metadata":[{"preferred_network":"login.microsoftonline.com","preferred_cache":"login.windows.net","aliases":["login.microsoftonline.com","login.windows.net","login.microsoft.com","sts.windows.net"]},{"preferred_network":"login.partner.microsoftonline.cn","preferred_cache":"login.partner.microsoftonline.cn","aliases":["login.partner.microsoftonline.cn","login.chinacloudapi.cn"]},{"preferred_network":"login.microsoftonline.de","preferred_cache":"login.microsoftonline.de","aliases":["login.microsoftonline.de"]},{"preferred_network":"login.microsoftonline.us","preferred_cache":"login.microsoftonline.us","aliases":["login.microsoftonline.us","login.usgovcloudapi.net"]},{"preferred_network":"login-us.microsoftonline.com","preferred_cache":"login-us.microsoftonline.com","aliases":["login-us.microsoftonline.com"]}]}',
60
+ authorityMetadata: '{"token_endpoint":"https://login.microsoftonline.com/common/oauth2/v2.0/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"],"jwks_uri":"https://login.microsoftonline.com/common/discovery/v2.0/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"],"scopes_supported":["openid","profile","email","offline_access"],"issuer":"https://login.microsoftonline.com/{tenantid}/v2.0","request_uri_parameter_supported":false,"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo","authorization_endpoint":"https://login.microsoftonline.com/common/oauth2/v2.0/authorize","device_authorization_endpoint":"https://login.microsoftonline.com/common/oauth2/v2.0/devicecode","http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/common/oauth2/v2.0/logout","claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"],"kerberos_endpoint":"https://login.microsoftonline.com/common/kerberos","tenant_region_scope":null,"cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net","msgraph_host":"graph.microsoft.com","rbac_url":"https://pas.windows.net"}',
61
+ clientCapabilities: [],
62
+ },
63
+ system: {
64
+ loggerOptions: {
65
+ logLevel: getMSALLogLevel(getLogLevel()),
66
+ },
67
+ },
68
+ });
69
+ }
70
+ cachedAvailableMSI(scopes, getTokenOptions) {
71
+ return __awaiter(this, void 0, void 0, function* () {
72
+ if (this.cachedMSI) {
73
+ return this.cachedMSI;
74
+ }
75
+ const MSIs = [
76
+ arcMsi,
77
+ fabricMsi,
78
+ appServiceMsi2019,
79
+ appServiceMsi2017,
80
+ cloudShellMsi,
81
+ tokenExchangeMsi(),
82
+ imdsMsi,
83
+ ];
84
+ for (const msi of MSIs) {
85
+ if (yield msi.isAvailable({
86
+ scopes,
87
+ identityClient: this.isAvailableIdentityClient,
88
+ clientId: this.clientId,
89
+ resourceId: this.resourceId,
90
+ getTokenOptions,
91
+ })) {
92
+ this.cachedMSI = msi;
93
+ return msi;
94
+ }
95
+ }
96
+ throw new CredentialUnavailableError(`ManagedIdentityCredential - No MSI credential available`);
97
+ });
98
+ }
99
+ authenticateManagedIdentity(scopes, getTokenOptions) {
100
+ return __awaiter(this, void 0, void 0, function* () {
101
+ const { span, updatedOptions } = tracingClient.startSpan(`ManagedIdentityCredential.authenticateManagedIdentity`, getTokenOptions);
102
+ try {
103
+ // Determining the available MSI, and avoiding checking for other MSIs while the program is running.
104
+ const availableMSI = yield this.cachedAvailableMSI(scopes, updatedOptions);
105
+ return availableMSI.getToken({
106
+ identityClient: this.identityClient,
107
+ scopes,
108
+ clientId: this.clientId,
109
+ resourceId: this.resourceId,
110
+ retryConfig: this.msiRetryConfig,
111
+ }, updatedOptions);
112
+ }
113
+ catch (err) {
114
+ span.setStatus({
115
+ status: "error",
116
+ error: err,
117
+ });
118
+ throw err;
119
+ }
120
+ finally {
121
+ span.end();
122
+ }
123
+ });
124
+ }
125
+ /**
126
+ * Authenticates with Microsoft Entra ID and returns an access token if successful.
127
+ * If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.
128
+ * If an unexpected error occurs, an {@link AuthenticationError} will be thrown with the details of the failure.
129
+ *
130
+ * @param scopes - The list of scopes for which the token will have access.
131
+ * @param options - The options used to configure any requests this
132
+ * TokenCredential implementation might make.
133
+ */
134
+ getToken(scopes, options) {
135
+ return __awaiter(this, void 0, void 0, function* () {
136
+ let result = null;
137
+ const { span, updatedOptions } = tracingClient.startSpan(`ManagedIdentityCredential.getToken`, options);
138
+ try {
139
+ // isEndpointAvailable can be true, false, or null,
140
+ // If it's null, it means we don't yet know whether
141
+ // the endpoint is available and need to check for it.
142
+ if (this.isEndpointUnavailable !== true) {
143
+ const availableMSI = yield this.cachedAvailableMSI(scopes, updatedOptions);
144
+ if (availableMSI.name === "tokenExchangeMsi") {
145
+ result = yield this.authenticateManagedIdentity(scopes, updatedOptions);
146
+ }
147
+ else {
148
+ const appTokenParameters = {
149
+ correlationId: this.identityClient.getCorrelationId(),
150
+ tenantId: (options === null || options === void 0 ? void 0 : options.tenantId) || "managed_identity",
151
+ scopes: Array.isArray(scopes) ? scopes : [scopes],
152
+ claims: options === null || options === void 0 ? void 0 : options.claims,
153
+ };
154
+ // Added a check to see if SetAppTokenProvider was already defined.
155
+ this.initializeSetAppTokenProvider();
156
+ const authenticationResult = yield this.confidentialApp.acquireTokenByClientCredential(Object.assign({}, appTokenParameters));
157
+ result = this.handleResult(scopes, authenticationResult || undefined);
158
+ }
159
+ if (result === null) {
160
+ // If authenticateManagedIdentity returns null,
161
+ // it means no MSI endpoints are available.
162
+ // If so, we avoid trying to reach to them in future requests.
163
+ this.isEndpointUnavailable = true;
164
+ // It also means that the endpoint answered with either 200 or 201 (see the sendTokenRequest method),
165
+ // yet we had no access token. For this reason, we'll throw once with a specific message:
166
+ const error = new CredentialUnavailableError("The managed identity endpoint was reached, yet no tokens were received.");
167
+ logger.getToken.info(formatError(scopes, error));
168
+ throw error;
169
+ }
170
+ // Since `authenticateManagedIdentity` didn't throw, and the result was not null,
171
+ // We will assume that this endpoint is reachable from this point forward,
172
+ // and avoid pinging again to it.
173
+ this.isEndpointUnavailable = false;
174
+ }
175
+ else {
176
+ // We've previously determined that the endpoint was unavailable,
177
+ // either because it was unreachable or permanently unable to authenticate.
178
+ const error = new CredentialUnavailableError("The managed identity endpoint is not currently available");
179
+ logger.getToken.info(formatError(scopes, error));
180
+ throw error;
181
+ }
182
+ logger.getToken.info(formatSuccess(scopes));
183
+ return result;
184
+ }
185
+ catch (err) {
186
+ // CredentialUnavailable errors are expected to reach here.
187
+ // We intend them to bubble up, so that DefaultAzureCredential can catch them.
188
+ if (err.name === "AuthenticationRequiredError") {
189
+ throw err;
190
+ }
191
+ // Expected errors to reach this point:
192
+ // - Errors coming from a method unexpectedly breaking.
193
+ // - When identityClient.sendTokenRequest throws, in which case
194
+ // if the status code was 400, it means that the endpoint is working,
195
+ // but no identity is available.
196
+ span.setStatus({
197
+ status: "error",
198
+ error: err,
199
+ });
200
+ // If either the network is unreachable,
201
+ // we can safely assume the credential is unavailable.
202
+ if (err.code === "ENETUNREACH") {
203
+ const error = new CredentialUnavailableError(`ManagedIdentityCredential: Unavailable. Network unreachable. Message: ${err.message}`);
204
+ logger.getToken.info(formatError(scopes, error));
205
+ throw error;
206
+ }
207
+ // If either the host was unreachable,
208
+ // we can safely assume the credential is unavailable.
209
+ if (err.code === "EHOSTUNREACH") {
210
+ const error = new CredentialUnavailableError(`ManagedIdentityCredential: Unavailable. No managed identity endpoint found. Message: ${err.message}`);
211
+ logger.getToken.info(formatError(scopes, error));
212
+ throw error;
213
+ }
214
+ // If err.statusCode has a value of 400, it comes from sendTokenRequest,
215
+ // and it means that the endpoint is working, but that no identity is available.
216
+ if (err.statusCode === 400) {
217
+ throw new CredentialUnavailableError(`ManagedIdentityCredential: The managed identity endpoint is indicating there's no available identity. Message: ${err.message}`);
218
+ }
219
+ // This is a special case for Docker Desktop which responds with a 403 with a message that contains "A socket operation was attempted to an unreachable network" or "A socket operation was attempted to an unreachable host"
220
+ // rather than just timing out, as expected.
221
+ if (err.statusCode === 403 || err.code === 403) {
222
+ if (err.message.includes("unreachable")) {
223
+ const error = new CredentialUnavailableError(`ManagedIdentityCredential: Unavailable. Network unreachable. Message: ${err.message}`);
224
+ logger.getToken.info(formatError(scopes, error));
225
+ throw error;
226
+ }
227
+ }
228
+ // If the error has no status code, we can assume there was no available identity.
229
+ // This will throw silently during any ChainedTokenCredential.
230
+ if (err.statusCode === undefined) {
231
+ throw new CredentialUnavailableError(`ManagedIdentityCredential: Authentication failed. Message ${err.message}`);
232
+ }
233
+ // Any other error should break the chain.
234
+ throw new AuthenticationError(err.statusCode, {
235
+ error: `ManagedIdentityCredential authentication failed.`,
236
+ error_description: err.message,
237
+ });
238
+ }
239
+ finally {
240
+ // Finally is always called, both if we return and if we throw in the above try/catch.
241
+ span.end();
242
+ }
243
+ });
244
+ }
245
+ /**
246
+ * Handles the MSAL authentication result.
247
+ * If the result has an account, we update the local account reference.
248
+ * If the token received is invalid, an error will be thrown depending on what's missing.
249
+ */
250
+ handleResult(scopes, result, getTokenOptions) {
251
+ this.ensureValidMsalToken(scopes, result, getTokenOptions);
252
+ logger.getToken.info(formatSuccess(scopes));
253
+ return {
254
+ token: result.accessToken,
255
+ expiresOnTimestamp: result.expiresOn.getTime(),
256
+ };
257
+ }
258
+ /**
259
+ * Ensures the validity of the MSAL token
260
+ */
261
+ ensureValidMsalToken(scopes, msalToken, getTokenOptions) {
262
+ const error = (message) => {
263
+ logger.getToken.info(message);
264
+ return new AuthenticationRequiredError({
265
+ scopes: Array.isArray(scopes) ? scopes : [scopes],
266
+ getTokenOptions,
267
+ message,
268
+ });
269
+ };
270
+ if (!msalToken) {
271
+ throw error("No response");
272
+ }
273
+ if (!msalToken.expiresOn) {
274
+ throw error(`Response had no "expiresOn" property.`);
275
+ }
276
+ if (!msalToken.accessToken) {
277
+ throw error(`Response had no "accessToken" property.`);
278
+ }
279
+ }
280
+ initializeSetAppTokenProvider() {
281
+ if (!this.isAppTokenProviderInitialized) {
282
+ this.confidentialApp.SetAppTokenProvider((appTokenProviderParameters) => __awaiter(this, void 0, void 0, function* () {
283
+ logger.info(`SetAppTokenProvider invoked with parameters- ${JSON.stringify(appTokenProviderParameters)}`);
284
+ const getTokenOptions = Object.assign({}, appTokenProviderParameters);
285
+ logger.info(`authenticateManagedIdentity invoked with scopes- ${JSON.stringify(appTokenProviderParameters.scopes)} and getTokenOptions - ${JSON.stringify(getTokenOptions)}`);
286
+ const resultToken = yield this.authenticateManagedIdentity(appTokenProviderParameters.scopes, getTokenOptions);
287
+ if (resultToken) {
288
+ logger.info(`SetAppTokenProvider will save the token in cache`);
289
+ const expiresInSeconds = (resultToken === null || resultToken === void 0 ? void 0 : resultToken.expiresOnTimestamp)
290
+ ? Math.floor((resultToken.expiresOnTimestamp - Date.now()) / 1000)
291
+ : 0;
292
+ return {
293
+ accessToken: resultToken === null || resultToken === void 0 ? void 0 : resultToken.token,
294
+ expiresInSeconds,
295
+ };
296
+ }
297
+ else {
298
+ logger.info(`SetAppTokenProvider token has "no_access_token_returned" as the saved token`);
299
+ return {
300
+ accessToken: "no_access_token_returned",
301
+ expiresInSeconds: 0,
302
+ };
303
+ }
304
+ }));
305
+ this.isAppTokenProviderInitialized = true;
306
+ }
307
+ }
308
+ }
309
+ //# sourceMappingURL=legacyMsiProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacyMsiProvider.js","sourceRoot":"","sources":["../../../../../../identity/src/credentials/managedIdentityCredential/legacyMsiProvider.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { AccessToken, GetTokenOptions } from \"@azure/core-auth\";\nimport { AppTokenProviderParameters, ConfidentialClientApplication } from \"@azure/msal-node\";\nimport {\n AuthenticationError,\n AuthenticationRequiredError,\n CredentialUnavailableError,\n} from \"../../errors\";\nimport { MSI, MSIConfiguration, MSIToken } from \"./models\";\nimport { MsalResult, MsalToken, ValidMsalToken } from \"../../msal/types\";\nimport { cloudShellMsi } from \"./cloudShellMsi\";\nimport { credentialLogger, formatError, formatSuccess } from \"../../util/logging\";\n\nimport { DeveloperSignOnClientId } from \"../../constants\";\nimport { IdentityClient } from \"../../client/identityClient\";\nimport { TokenCredentialOptions } from \"../../tokenCredentialOptions\";\nimport { appServiceMsi2017 } from \"./appServiceMsi2017\";\nimport { appServiceMsi2019 } from \"./appServiceMsi2019\";\nimport { arcMsi } from \"./arcMsi\";\nimport { fabricMsi } from \"./fabricMsi\";\nimport { getLogLevel } from \"@azure/logger\";\nimport { getMSALLogLevel } from \"../../msal/utils\";\nimport { imdsMsi } from \"./imdsMsi\";\nimport { tokenExchangeMsi } from \"./tokenExchangeMsi\";\nimport { tracingClient } from \"../../util/tracing\";\n\nconst logger = credentialLogger(\"ManagedIdentityCredential\");\n\n// As part of the migration of Managed Identity to MSAL, this legacy provider captures the existing behavior\n// ported over from the ManagedIdentityCredential verbatim. This is to ensure that the existing behavior\n// is maintained while the new implementation is being tested and validated. Part of the migration (tracked in #25253)\n// should include deleting this provider once it is no longer needed.\n\n/**\n * Options to send on the {@link ManagedIdentityCredential} constructor.\n * Since this is an internal implementation, uses a looser interface than the public one.\n */\ninterface ManagedIdentityCredentialOptions extends TokenCredentialOptions {\n /**\n * The client ID of the user - assigned identity, or app registration(when working with AKS pod - identity).\n */\n clientId?: string;\n\n /**\n * Allows specifying a custom resource Id.\n * In scenarios such as when user assigned identities are created using an ARM template,\n * where the resource Id of the identity is known but the client Id can't be known ahead of time,\n * this parameter allows programs to use these user assigned identities\n * without having to first determine the client Id of the created identity.\n */\n resourceId?: string;\n}\n\nexport class LegacyMsiProvider {\n private identityClient: IdentityClient;\n private clientId: string | undefined;\n private resourceId: string | undefined;\n private isEndpointUnavailable: boolean | null = null;\n private isAvailableIdentityClient: IdentityClient;\n private confidentialApp: ConfidentialClientApplication;\n private isAppTokenProviderInitialized: boolean = false;\n private msiRetryConfig: MSIConfiguration[\"retryConfig\"] = {\n maxRetries: 5,\n startDelayInMs: 800,\n intervalIncrement: 2,\n };\n\n constructor(\n clientIdOrOptions?: string | ManagedIdentityCredentialOptions,\n options?: TokenCredentialOptions,\n ) {\n let _options: TokenCredentialOptions | undefined;\n if (typeof clientIdOrOptions === \"string\") {\n this.clientId = clientIdOrOptions;\n _options = options;\n } else {\n this.clientId = (clientIdOrOptions as ManagedIdentityCredentialOptions)?.clientId;\n _options = clientIdOrOptions;\n }\n this.resourceId = (_options as ManagedIdentityCredentialOptions)?.resourceId;\n // For JavaScript users.\n if (this.clientId && this.resourceId) {\n throw new Error(\n `ManagedIdentityCredential - Client Id and Resource Id can't be provided at the same time.`,\n );\n }\n if (_options?.retryOptions?.maxRetries !== undefined) {\n this.msiRetryConfig.maxRetries = _options.retryOptions.maxRetries;\n }\n this.identityClient = new IdentityClient(_options);\n this.isAvailableIdentityClient = new IdentityClient({\n ..._options,\n retryOptions: {\n maxRetries: 0,\n },\n });\n\n /** authority host validation and metadata discovery to be skipped in managed identity\n * since this wasn't done previously before adding token cache support\n */\n this.confidentialApp = new ConfidentialClientApplication({\n auth: {\n authority: \"https://login.microsoftonline.com/managed_identity\",\n clientId: this.clientId ?? DeveloperSignOnClientId,\n clientSecret: \"dummy-secret\",\n cloudDiscoveryMetadata:\n '{\"tenant_discovery_endpoint\":\"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration\",\"api-version\":\"1.1\",\"metadata\":[{\"preferred_network\":\"login.microsoftonline.com\",\"preferred_cache\":\"login.windows.net\",\"aliases\":[\"login.microsoftonline.com\",\"login.windows.net\",\"login.microsoft.com\",\"sts.windows.net\"]},{\"preferred_network\":\"login.partner.microsoftonline.cn\",\"preferred_cache\":\"login.partner.microsoftonline.cn\",\"aliases\":[\"login.partner.microsoftonline.cn\",\"login.chinacloudapi.cn\"]},{\"preferred_network\":\"login.microsoftonline.de\",\"preferred_cache\":\"login.microsoftonline.de\",\"aliases\":[\"login.microsoftonline.de\"]},{\"preferred_network\":\"login.microsoftonline.us\",\"preferred_cache\":\"login.microsoftonline.us\",\"aliases\":[\"login.microsoftonline.us\",\"login.usgovcloudapi.net\"]},{\"preferred_network\":\"login-us.microsoftonline.com\",\"preferred_cache\":\"login-us.microsoftonline.com\",\"aliases\":[\"login-us.microsoftonline.com\"]}]}',\n authorityMetadata:\n '{\"token_endpoint\":\"https://login.microsoftonline.com/common/oauth2/v2.0/token\",\"token_endpoint_auth_methods_supported\":[\"client_secret_post\",\"private_key_jwt\",\"client_secret_basic\"],\"jwks_uri\":\"https://login.microsoftonline.com/common/discovery/v2.0/keys\",\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\"],\"subject_types_supported\":[\"pairwise\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"response_types_supported\":[\"code\",\"id_token\",\"code id_token\",\"id_token token\"],\"scopes_supported\":[\"openid\",\"profile\",\"email\",\"offline_access\"],\"issuer\":\"https://login.microsoftonline.com/{tenantid}/v2.0\",\"request_uri_parameter_supported\":false,\"userinfo_endpoint\":\"https://graph.microsoft.com/oidc/userinfo\",\"authorization_endpoint\":\"https://login.microsoftonline.com/common/oauth2/v2.0/authorize\",\"device_authorization_endpoint\":\"https://login.microsoftonline.com/common/oauth2/v2.0/devicecode\",\"http_logout_supported\":true,\"frontchannel_logout_supported\":true,\"end_session_endpoint\":\"https://login.microsoftonline.com/common/oauth2/v2.0/logout\",\"claims_supported\":[\"sub\",\"iss\",\"cloud_instance_name\",\"cloud_instance_host_name\",\"cloud_graph_host_name\",\"msgraph_host\",\"aud\",\"exp\",\"iat\",\"auth_time\",\"acr\",\"nonce\",\"preferred_username\",\"name\",\"tid\",\"ver\",\"at_hash\",\"c_hash\",\"email\"],\"kerberos_endpoint\":\"https://login.microsoftonline.com/common/kerberos\",\"tenant_region_scope\":null,\"cloud_instance_name\":\"microsoftonline.com\",\"cloud_graph_host_name\":\"graph.windows.net\",\"msgraph_host\":\"graph.microsoft.com\",\"rbac_url\":\"https://pas.windows.net\"}',\n clientCapabilities: [],\n },\n system: {\n loggerOptions: {\n logLevel: getMSALLogLevel(getLogLevel()),\n },\n },\n });\n }\n\n private cachedMSI: MSI | undefined;\n\n private async cachedAvailableMSI(\n scopes: string | string[],\n getTokenOptions?: GetTokenOptions,\n ): Promise<MSI> {\n if (this.cachedMSI) {\n return this.cachedMSI;\n }\n\n const MSIs = [\n arcMsi,\n fabricMsi,\n appServiceMsi2019,\n appServiceMsi2017,\n cloudShellMsi,\n tokenExchangeMsi(),\n imdsMsi,\n ];\n\n for (const msi of MSIs) {\n if (\n await msi.isAvailable({\n scopes,\n identityClient: this.isAvailableIdentityClient,\n clientId: this.clientId,\n resourceId: this.resourceId,\n getTokenOptions,\n })\n ) {\n this.cachedMSI = msi;\n return msi;\n }\n }\n\n throw new CredentialUnavailableError(`ManagedIdentityCredential - No MSI credential available`);\n }\n\n private async authenticateManagedIdentity(\n scopes: string | string[],\n getTokenOptions?: GetTokenOptions,\n ): Promise<MSIToken | null> {\n const { span, updatedOptions } = tracingClient.startSpan(\n `ManagedIdentityCredential.authenticateManagedIdentity`,\n getTokenOptions,\n );\n\n try {\n // Determining the available MSI, and avoiding checking for other MSIs while the program is running.\n const availableMSI = await this.cachedAvailableMSI(scopes, updatedOptions);\n return availableMSI.getToken(\n {\n identityClient: this.identityClient,\n scopes,\n clientId: this.clientId,\n resourceId: this.resourceId,\n retryConfig: this.msiRetryConfig,\n },\n updatedOptions,\n );\n } catch (err: any) {\n span.setStatus({\n status: \"error\",\n error: err,\n });\n throw err;\n } finally {\n span.end();\n }\n }\n\n /**\n * Authenticates with Microsoft Entra ID and returns an access token if successful.\n * If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.\n * If an unexpected error occurs, an {@link AuthenticationError} will be thrown with the details of the failure.\n *\n * @param scopes - The list of scopes for which the token will have access.\n * @param options - The options used to configure any requests this\n * TokenCredential implementation might make.\n */\n public async getToken(\n scopes: string | string[],\n options?: GetTokenOptions,\n ): Promise<AccessToken> {\n let result: AccessToken | null = null;\n const { span, updatedOptions } = tracingClient.startSpan(\n `ManagedIdentityCredential.getToken`,\n options,\n );\n try {\n // isEndpointAvailable can be true, false, or null,\n // If it's null, it means we don't yet know whether\n // the endpoint is available and need to check for it.\n if (this.isEndpointUnavailable !== true) {\n const availableMSI = await this.cachedAvailableMSI(scopes, updatedOptions);\n if (availableMSI.name === \"tokenExchangeMsi\") {\n result = await this.authenticateManagedIdentity(scopes, updatedOptions);\n } else {\n const appTokenParameters: AppTokenProviderParameters = {\n correlationId: this.identityClient.getCorrelationId(),\n tenantId: options?.tenantId || \"managed_identity\",\n scopes: Array.isArray(scopes) ? scopes : [scopes],\n claims: options?.claims,\n };\n\n // Added a check to see if SetAppTokenProvider was already defined.\n this.initializeSetAppTokenProvider();\n const authenticationResult = await this.confidentialApp.acquireTokenByClientCredential({\n ...appTokenParameters,\n });\n result = this.handleResult(scopes, authenticationResult || undefined);\n }\n if (result === null) {\n // If authenticateManagedIdentity returns null,\n // it means no MSI endpoints are available.\n // If so, we avoid trying to reach to them in future requests.\n this.isEndpointUnavailable = true;\n\n // It also means that the endpoint answered with either 200 or 201 (see the sendTokenRequest method),\n // yet we had no access token. For this reason, we'll throw once with a specific message:\n const error = new CredentialUnavailableError(\n \"The managed identity endpoint was reached, yet no tokens were received.\",\n );\n logger.getToken.info(formatError(scopes, error));\n throw error;\n }\n\n // Since `authenticateManagedIdentity` didn't throw, and the result was not null,\n // We will assume that this endpoint is reachable from this point forward,\n // and avoid pinging again to it.\n this.isEndpointUnavailable = false;\n } else {\n // We've previously determined that the endpoint was unavailable,\n // either because it was unreachable or permanently unable to authenticate.\n const error = new CredentialUnavailableError(\n \"The managed identity endpoint is not currently available\",\n );\n logger.getToken.info(formatError(scopes, error));\n throw error;\n }\n\n logger.getToken.info(formatSuccess(scopes));\n return result;\n } catch (err: any) {\n // CredentialUnavailable errors are expected to reach here.\n // We intend them to bubble up, so that DefaultAzureCredential can catch them.\n if (err.name === \"AuthenticationRequiredError\") {\n throw err;\n }\n\n // Expected errors to reach this point:\n // - Errors coming from a method unexpectedly breaking.\n // - When identityClient.sendTokenRequest throws, in which case\n // if the status code was 400, it means that the endpoint is working,\n // but no identity is available.\n\n span.setStatus({\n status: \"error\",\n error: err,\n });\n\n // If either the network is unreachable,\n // we can safely assume the credential is unavailable.\n if (err.code === \"ENETUNREACH\") {\n const error = new CredentialUnavailableError(\n `ManagedIdentityCredential: Unavailable. Network unreachable. Message: ${err.message}`,\n );\n\n logger.getToken.info(formatError(scopes, error));\n throw error;\n }\n\n // If either the host was unreachable,\n // we can safely assume the credential is unavailable.\n if (err.code === \"EHOSTUNREACH\") {\n const error = new CredentialUnavailableError(\n `ManagedIdentityCredential: Unavailable. No managed identity endpoint found. Message: ${err.message}`,\n );\n\n logger.getToken.info(formatError(scopes, error));\n throw error;\n }\n // If err.statusCode has a value of 400, it comes from sendTokenRequest,\n // and it means that the endpoint is working, but that no identity is available.\n if (err.statusCode === 400) {\n throw new CredentialUnavailableError(\n `ManagedIdentityCredential: The managed identity endpoint is indicating there's no available identity. Message: ${err.message}`,\n );\n }\n\n // This is a special case for Docker Desktop which responds with a 403 with a message that contains \"A socket operation was attempted to an unreachable network\" or \"A socket operation was attempted to an unreachable host\"\n // rather than just timing out, as expected.\n if (err.statusCode === 403 || err.code === 403) {\n if (err.message.includes(\"unreachable\")) {\n const error = new CredentialUnavailableError(\n `ManagedIdentityCredential: Unavailable. Network unreachable. Message: ${err.message}`,\n );\n\n logger.getToken.info(formatError(scopes, error));\n throw error;\n }\n }\n\n // If the error has no status code, we can assume there was no available identity.\n // This will throw silently during any ChainedTokenCredential.\n if (err.statusCode === undefined) {\n throw new CredentialUnavailableError(\n `ManagedIdentityCredential: Authentication failed. Message ${err.message}`,\n );\n }\n\n // Any other error should break the chain.\n throw new AuthenticationError(err.statusCode, {\n error: `ManagedIdentityCredential authentication failed.`,\n error_description: err.message,\n });\n } finally {\n // Finally is always called, both if we return and if we throw in the above try/catch.\n span.end();\n }\n }\n\n /**\n * Handles the MSAL authentication result.\n * If the result has an account, we update the local account reference.\n * If the token received is invalid, an error will be thrown depending on what's missing.\n */\n private handleResult(\n scopes: string | string[],\n result?: MsalResult,\n getTokenOptions?: GetTokenOptions,\n ): AccessToken {\n this.ensureValidMsalToken(scopes, result, getTokenOptions);\n logger.getToken.info(formatSuccess(scopes));\n return {\n token: result.accessToken,\n expiresOnTimestamp: result.expiresOn.getTime(),\n };\n }\n\n /**\n * Ensures the validity of the MSAL token\n */\n private ensureValidMsalToken(\n scopes: string | string[],\n msalToken?: MsalToken,\n getTokenOptions?: GetTokenOptions,\n ): asserts msalToken is ValidMsalToken {\n const error = (message: string): Error => {\n logger.getToken.info(message);\n return new AuthenticationRequiredError({\n scopes: Array.isArray(scopes) ? scopes : [scopes],\n getTokenOptions,\n message,\n });\n };\n if (!msalToken) {\n throw error(\"No response\");\n }\n if (!msalToken.expiresOn) {\n throw error(`Response had no \"expiresOn\" property.`);\n }\n if (!msalToken.accessToken) {\n throw error(`Response had no \"accessToken\" property.`);\n }\n }\n\n private initializeSetAppTokenProvider(): void {\n if (!this.isAppTokenProviderInitialized) {\n this.confidentialApp.SetAppTokenProvider(async (appTokenProviderParameters) => {\n logger.info(\n `SetAppTokenProvider invoked with parameters- ${JSON.stringify(\n appTokenProviderParameters,\n )}`,\n );\n const getTokenOptions: GetTokenOptions = {\n ...appTokenProviderParameters,\n };\n logger.info(\n `authenticateManagedIdentity invoked with scopes- ${JSON.stringify(\n appTokenProviderParameters.scopes,\n )} and getTokenOptions - ${JSON.stringify(getTokenOptions)}`,\n );\n const resultToken = await this.authenticateManagedIdentity(\n appTokenProviderParameters.scopes,\n getTokenOptions,\n );\n\n if (resultToken) {\n logger.info(`SetAppTokenProvider will save the token in cache`);\n\n const expiresInSeconds = resultToken?.expiresOnTimestamp\n ? Math.floor((resultToken.expiresOnTimestamp - Date.now()) / 1000)\n : 0;\n return {\n accessToken: resultToken?.token,\n expiresInSeconds,\n };\n } else {\n logger.info(\n `SetAppTokenProvider token has \"no_access_token_returned\" as the saved token`,\n );\n return {\n accessToken: \"no_access_token_returned\",\n expiresInSeconds: 0,\n };\n }\n });\n this.isAppTokenProviderInitialized = true;\n }\n }\n}\n"]}
@@ -2,10 +2,12 @@
2
2
  // Licensed under the MIT license.
3
3
  import { __awaiter } from "tslib";
4
4
  import { processMultiTenantRequest, resolveAdditionallyAllowedTenantIds, } from "../util/tenantIdUtils";
5
- import { MsalOnBehalfOf } from "../msal/nodeFlows/msalOnBehalfOf";
6
- import { credentialLogger } from "../util/logging";
5
+ import { credentialLogger, formatError } from "../util/logging";
7
6
  import { ensureScopes } from "../util/scopeUtils";
8
7
  import { tracingClient } from "../util/tracing";
8
+ import { createMsalClient } from "../msal/nodeFlows/msalClient";
9
+ import { readFile } from "node:fs/promises";
10
+ import { createHash } from "node:crypto";
9
11
  const credentialName = "OnBehalfOfCredential";
10
12
  const logger = credentialLogger(credentialName);
11
13
  /**
@@ -13,16 +15,19 @@ const logger = credentialLogger(credentialName);
13
15
  */
14
16
  export class OnBehalfOfCredential {
15
17
  constructor(options) {
16
- this.options = options;
17
18
  const { clientSecret } = options;
18
- const { certificatePath } = options;
19
+ const { certificatePath, sendCertificateChain } = options;
19
20
  const { tenantId, clientId, userAssertionToken, additionallyAllowedTenants: additionallyAllowedTenantIds, } = options;
20
21
  if (!tenantId || !clientId || !(clientSecret || certificatePath) || !userAssertionToken) {
21
22
  throw new Error(`${credentialName}: tenantId, clientId, clientSecret (or certificatePath) and userAssertionToken are required parameters.`);
22
23
  }
24
+ this.certificatePath = certificatePath;
25
+ this.clientSecret = clientSecret;
26
+ this.userAssertionToken = userAssertionToken;
27
+ this.sendCertificateChain = sendCertificateChain;
23
28
  this.tenantId = tenantId;
24
29
  this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(additionallyAllowedTenantIds);
25
- this.msalFlow = new MsalOnBehalfOf(Object.assign(Object.assign({}, this.options), { logger, tokenCredentialOptions: this.options }));
30
+ this.msalClient = createMsalClient(clientId, this.tenantId, Object.assign(Object.assign({}, options), { logger, tokenCredentialOptions: options }));
26
31
  }
27
32
  /**
28
33
  * Authenticates with Microsoft Entra ID and returns an access token if successful.
@@ -36,9 +41,64 @@ export class OnBehalfOfCredential {
36
41
  return tracingClient.withSpan(`${credentialName}.getToken`, options, (newOptions) => __awaiter(this, void 0, void 0, function* () {
37
42
  newOptions.tenantId = processMultiTenantRequest(this.tenantId, newOptions, this.additionallyAllowedTenantIds, logger);
38
43
  const arrayScopes = ensureScopes(scopes);
39
- return this.msalFlow.getToken(arrayScopes, newOptions);
44
+ if (this.certificatePath) {
45
+ const clientCertificate = yield this.buildClientCertificate(this.certificatePath);
46
+ return this.msalClient.getTokenOnBehalfOf(arrayScopes, this.userAssertionToken, clientCertificate, newOptions);
47
+ }
48
+ else if (this.clientSecret) {
49
+ return this.msalClient.getTokenOnBehalfOf(arrayScopes, this.userAssertionToken, this.clientSecret, options);
50
+ }
51
+ else {
52
+ // this is a bug, as the constructor should have thrown an error if neither clientSecret nor certificatePath were provided
53
+ throw new Error("Expected either clientSecret or certificatePath to be defined.");
54
+ }
40
55
  }));
41
56
  });
42
57
  }
58
+ buildClientCertificate(certificatePath) {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ try {
61
+ const parts = yield this.parseCertificate({ certificatePath }, this.sendCertificateChain);
62
+ return {
63
+ thumbprint: parts.thumbprint,
64
+ privateKey: parts.certificateContents,
65
+ x5c: parts.x5c,
66
+ };
67
+ }
68
+ catch (error) {
69
+ logger.info(formatError("", error));
70
+ throw error;
71
+ }
72
+ });
73
+ }
74
+ parseCertificate(configuration, sendCertificateChain) {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ const certificatePath = configuration.certificatePath;
77
+ const certificateContents = yield readFile(certificatePath, "utf8");
78
+ const x5c = sendCertificateChain ? certificateContents : undefined;
79
+ const certificatePattern = /(-+BEGIN CERTIFICATE-+)(\n\r?|\r\n?)([A-Za-z0-9+/\n\r]+=*)(\n\r?|\r\n?)(-+END CERTIFICATE-+)/g;
80
+ const publicKeys = [];
81
+ // Match all possible certificates, in the order they are in the file. These will form the chain that is used for x5c
82
+ let match;
83
+ do {
84
+ match = certificatePattern.exec(certificateContents);
85
+ if (match) {
86
+ publicKeys.push(match[3]);
87
+ }
88
+ } while (match);
89
+ if (publicKeys.length === 0) {
90
+ throw new Error("The file at the specified path does not contain a PEM-encoded certificate.");
91
+ }
92
+ const thumbprint = createHash("sha1")
93
+ .update(Buffer.from(publicKeys[0], "base64"))
94
+ .digest("hex")
95
+ .toUpperCase();
96
+ return {
97
+ certificateContents,
98
+ thumbprint,
99
+ x5c,
100
+ };
101
+ });
102
+ }
43
103
  }
44
104
  //# sourceMappingURL=onBehalfOfCredential.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"onBehalfOfCredential.js","sourceRoot":"","sources":["../../../../../identity/src/credentials/onBehalfOfCredential.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;;AAQlC,OAAO,EACL,yBAAyB,EACzB,mCAAmC,GACpC,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAElE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAC9C,MAAM,MAAM,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC;AAEhD;;GAEG;AACH,MAAM,OAAO,oBAAoB;IAyD/B,YAAoB,OAAoC;QAApC,YAAO,GAAP,OAAO,CAA6B;QACtD,MAAM,EAAE,YAAY,EAAE,GAAG,OAA4C,CAAC;QACtE,MAAM,EAAE,eAAe,EAAE,GAAG,OAAiD,CAAC;QAC9E,MAAM,EACJ,QAAQ,EACR,QAAQ,EACR,kBAAkB,EAClB,0BAA0B,EAAE,4BAA4B,GACzD,GAAG,OAAO,CAAC;QACZ,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,YAAY,IAAI,eAAe,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxF,MAAM,IAAI,KAAK,CACb,GAAG,cAAc,yGAAyG,CAC3H,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,4BAA4B,GAAG,mCAAmC,CACrE,4BAA4B,CAC7B,CAAC;QAEF,IAAI,CAAC,QAAQ,GAAG,IAAI,cAAc,iCAC7B,IAAI,CAAC,OAAO,KACf,MAAM,EACN,sBAAsB,EAAE,IAAI,CAAC,OAAO,IACpC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACG,QAAQ;6DAAC,MAAyB,EAAE,UAA2B,EAAE;YACrE,OAAO,aAAa,CAAC,QAAQ,CAAC,GAAG,cAAc,WAAW,EAAE,OAAO,EAAE,CAAO,UAAU,EAAE,EAAE;gBACxF,UAAU,CAAC,QAAQ,GAAG,yBAAyB,CAC7C,IAAI,CAAC,QAAQ,EACb,UAAU,EACV,IAAI,CAAC,4BAA4B,EACjC,MAAM,CACP,CAAC;gBAEF,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;gBACzC,OAAO,IAAI,CAAC,QAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;YAC1D,CAAC,CAAA,CAAC,CAAC;QACL,CAAC;KAAA;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport {\n OnBehalfOfCredentialCertificateOptions,\n OnBehalfOfCredentialOptions,\n OnBehalfOfCredentialSecretOptions,\n} from \"./onBehalfOfCredentialOptions\";\nimport {\n processMultiTenantRequest,\n resolveAdditionallyAllowedTenantIds,\n} from \"../util/tenantIdUtils\";\nimport { CredentialPersistenceOptions } from \"./credentialPersistenceOptions\";\nimport { MsalFlow } from \"../msal/flows\";\nimport { MsalOnBehalfOf } from \"../msal/nodeFlows/msalOnBehalfOf\";\nimport { MultiTenantTokenCredentialOptions } from \"./multiTenantTokenCredentialOptions\";\nimport { credentialLogger } from \"../util/logging\";\nimport { ensureScopes } from \"../util/scopeUtils\";\nimport { tracingClient } from \"../util/tracing\";\n\nconst credentialName = \"OnBehalfOfCredential\";\nconst logger = credentialLogger(credentialName);\n\n/**\n * Enables authentication to Microsoft Entra ID using the [On Behalf Of flow](https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow).\n */\nexport class OnBehalfOfCredential implements TokenCredential {\n private tenantId: string;\n private additionallyAllowedTenantIds: string[];\n private msalFlow: MsalFlow;\n /**\n * Creates an instance of the {@link OnBehalfOfCredential} with the details\n * needed to authenticate against Microsoft Entra ID with path to a PEM certificate,\n * and an user assertion.\n *\n * Example using the `KeyClient` from [\\@azure/keyvault-keys](https://www.npmjs.com/package/\\@azure/keyvault-keys):\n *\n * ```ts\n * const tokenCredential = new OnBehalfOfCredential({\n * tenantId,\n * clientId,\n * certificatePath: \"/path/to/certificate.pem\",\n * userAssertionToken: \"access-token\"\n * });\n * const client = new KeyClient(\"vault-url\", tokenCredential);\n *\n * await client.getKey(\"key-name\");\n * ```\n *\n * @param options - Optional parameters, generally common across credentials.\n */\n constructor(\n options: OnBehalfOfCredentialCertificateOptions &\n MultiTenantTokenCredentialOptions &\n CredentialPersistenceOptions,\n );\n /**\n * Creates an instance of the {@link OnBehalfOfCredential} with the details\n * needed to authenticate against Microsoft Entra ID with a client\n * secret and an user assertion.\n *\n * Example using the `KeyClient` from [\\@azure/keyvault-keys](https://www.npmjs.com/package/\\@azure/keyvault-keys):\n *\n * ```ts\n * const tokenCredential = new OnBehalfOfCredential({\n * tenantId,\n * clientId,\n * clientSecret,\n * userAssertionToken: \"access-token\"\n * });\n * const client = new KeyClient(\"vault-url\", tokenCredential);\n *\n * await client.getKey(\"key-name\");\n * ```\n *\n * @param options - Optional parameters, generally common across credentials.\n */\n constructor(\n options: OnBehalfOfCredentialSecretOptions &\n MultiTenantTokenCredentialOptions &\n CredentialPersistenceOptions,\n );\n\n constructor(private options: OnBehalfOfCredentialOptions) {\n const { clientSecret } = options as OnBehalfOfCredentialSecretOptions;\n const { certificatePath } = options as OnBehalfOfCredentialCertificateOptions;\n const {\n tenantId,\n clientId,\n userAssertionToken,\n additionallyAllowedTenants: additionallyAllowedTenantIds,\n } = options;\n if (!tenantId || !clientId || !(clientSecret || certificatePath) || !userAssertionToken) {\n throw new Error(\n `${credentialName}: tenantId, clientId, clientSecret (or certificatePath) and userAssertionToken are required parameters.`,\n );\n }\n\n this.tenantId = tenantId;\n this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(\n additionallyAllowedTenantIds,\n );\n\n this.msalFlow = new MsalOnBehalfOf({\n ...this.options,\n logger,\n tokenCredentialOptions: this.options,\n });\n }\n\n /**\n * Authenticates with Microsoft Entra ID and returns an access token if successful.\n * If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.\n *\n * @param scopes - The list of scopes for which the token will have access.\n * @param options - The options used to configure the underlying network requests.\n */\n async getToken(scopes: string | string[], options: GetTokenOptions = {}): Promise<AccessToken> {\n return tracingClient.withSpan(`${credentialName}.getToken`, options, async (newOptions) => {\n newOptions.tenantId = processMultiTenantRequest(\n this.tenantId,\n newOptions,\n this.additionallyAllowedTenantIds,\n logger,\n );\n\n const arrayScopes = ensureScopes(scopes);\n return this.msalFlow!.getToken(arrayScopes, newOptions);\n });\n }\n}\n"]}
1
+ {"version":3,"file":"onBehalfOfCredential.js","sourceRoot":"","sources":["../../../../../identity/src/credentials/onBehalfOfCredential.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport {\n OnBehalfOfCredentialCertificateOptions,\n OnBehalfOfCredentialOptions,\n OnBehalfOfCredentialSecretOptions,\n} from \"./onBehalfOfCredentialOptions\";\nimport {\n processMultiTenantRequest,\n resolveAdditionallyAllowedTenantIds,\n} from \"../util/tenantIdUtils\";\nimport { CredentialPersistenceOptions } from \"./credentialPersistenceOptions\";\nimport { MultiTenantTokenCredentialOptions } from \"./multiTenantTokenCredentialOptions\";\nimport { credentialLogger, formatError } from \"../util/logging\";\nimport { ensureScopes } from \"../util/scopeUtils\";\nimport { tracingClient } from \"../util/tracing\";\nimport { MsalClient, createMsalClient } from \"../msal/nodeFlows/msalClient\";\nimport { CertificateParts } from \"../msal/types\";\nimport { ClientCertificatePEMCertificatePath } from \"./clientCertificateCredential\";\nimport { readFile } from \"node:fs/promises\";\nimport { createHash } from \"node:crypto\";\n\nconst credentialName = \"OnBehalfOfCredential\";\nconst logger = credentialLogger(credentialName);\n\n/**\n * Enables authentication to Microsoft Entra ID using the [On Behalf Of flow](https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow).\n */\nexport class OnBehalfOfCredential implements TokenCredential {\n private tenantId: string;\n private additionallyAllowedTenantIds: string[];\n private msalClient: MsalClient;\n private sendCertificateChain?: boolean;\n private certificatePath?: string;\n private clientSecret?: string;\n private userAssertionToken: string;\n\n /**\n * Creates an instance of the {@link OnBehalfOfCredential} with the details\n * needed to authenticate against Microsoft Entra ID with path to a PEM certificate,\n * and an user assertion.\n *\n * Example using the `KeyClient` from [\\@azure/keyvault-keys](https://www.npmjs.com/package/\\@azure/keyvault-keys):\n *\n * ```ts\n * const tokenCredential = new OnBehalfOfCredential({\n * tenantId,\n * clientId,\n * certificatePath: \"/path/to/certificate.pem\",\n * userAssertionToken: \"access-token\"\n * });\n * const client = new KeyClient(\"vault-url\", tokenCredential);\n *\n * await client.getKey(\"key-name\");\n * ```\n *\n * @param options - Optional parameters, generally common across credentials.\n */\n constructor(\n options: OnBehalfOfCredentialCertificateOptions &\n MultiTenantTokenCredentialOptions &\n CredentialPersistenceOptions,\n );\n /**\n * Creates an instance of the {@link OnBehalfOfCredential} with the details\n * needed to authenticate against Microsoft Entra ID with a client\n * secret and an user assertion.\n *\n * Example using the `KeyClient` from [\\@azure/keyvault-keys](https://www.npmjs.com/package/\\@azure/keyvault-keys):\n *\n * ```ts\n * const tokenCredential = new OnBehalfOfCredential({\n * tenantId,\n * clientId,\n * clientSecret,\n * userAssertionToken: \"access-token\"\n * });\n * const client = new KeyClient(\"vault-url\", tokenCredential);\n *\n * await client.getKey(\"key-name\");\n * ```\n *\n * @param options - Optional parameters, generally common across credentials.\n */\n constructor(\n options: OnBehalfOfCredentialSecretOptions &\n MultiTenantTokenCredentialOptions &\n CredentialPersistenceOptions,\n );\n\n constructor(options: OnBehalfOfCredentialOptions) {\n const { clientSecret } = options as OnBehalfOfCredentialSecretOptions;\n const { certificatePath, sendCertificateChain } =\n options as OnBehalfOfCredentialCertificateOptions;\n const {\n tenantId,\n clientId,\n userAssertionToken,\n additionallyAllowedTenants: additionallyAllowedTenantIds,\n } = options;\n if (!tenantId || !clientId || !(clientSecret || certificatePath) || !userAssertionToken) {\n throw new Error(\n `${credentialName}: tenantId, clientId, clientSecret (or certificatePath) and userAssertionToken are required parameters.`,\n );\n }\n this.certificatePath = certificatePath;\n this.clientSecret = clientSecret;\n this.userAssertionToken = userAssertionToken;\n this.sendCertificateChain = sendCertificateChain;\n\n this.tenantId = tenantId;\n this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(\n additionallyAllowedTenantIds,\n );\n\n this.msalClient = createMsalClient(clientId, this.tenantId, {\n ...options,\n logger,\n tokenCredentialOptions: options,\n });\n }\n\n /**\n * Authenticates with Microsoft Entra ID and returns an access token if successful.\n * If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.\n *\n * @param scopes - The list of scopes for which the token will have access.\n * @param options - The options used to configure the underlying network requests.\n */\n async getToken(scopes: string | string[], options: GetTokenOptions = {}): Promise<AccessToken> {\n return tracingClient.withSpan(`${credentialName}.getToken`, options, async (newOptions) => {\n newOptions.tenantId = processMultiTenantRequest(\n this.tenantId,\n newOptions,\n this.additionallyAllowedTenantIds,\n logger,\n );\n\n const arrayScopes = ensureScopes(scopes);\n if (this.certificatePath) {\n const clientCertificate = await this.buildClientCertificate(this.certificatePath);\n\n return this.msalClient.getTokenOnBehalfOf(\n arrayScopes,\n this.userAssertionToken,\n clientCertificate,\n newOptions,\n );\n } else if (this.clientSecret) {\n return this.msalClient.getTokenOnBehalfOf(\n arrayScopes,\n this.userAssertionToken,\n this.clientSecret,\n options,\n );\n } else {\n // this is a bug, as the constructor should have thrown an error if neither clientSecret nor certificatePath were provided\n throw new Error(\"Expected either clientSecret or certificatePath to be defined.\");\n }\n });\n }\n\n private async buildClientCertificate(certificatePath: string): Promise<CertificateParts> {\n try {\n const parts = await this.parseCertificate({ certificatePath }, this.sendCertificateChain);\n return {\n thumbprint: parts.thumbprint,\n privateKey: parts.certificateContents,\n x5c: parts.x5c,\n };\n } catch (error: any) {\n logger.info(formatError(\"\", error));\n throw error;\n }\n }\n\n private async parseCertificate(\n configuration: ClientCertificatePEMCertificatePath,\n sendCertificateChain?: boolean,\n ): Promise<Omit<CertificateParts, \"privateKey\"> & { certificateContents: string }> {\n const certificatePath = configuration.certificatePath;\n const certificateContents = await readFile(certificatePath, \"utf8\");\n const x5c = sendCertificateChain ? certificateContents : undefined;\n\n const certificatePattern =\n /(-+BEGIN CERTIFICATE-+)(\\n\\r?|\\r\\n?)([A-Za-z0-9+/\\n\\r]+=*)(\\n\\r?|\\r\\n?)(-+END CERTIFICATE-+)/g;\n const publicKeys: string[] = [];\n\n // Match all possible certificates, in the order they are in the file. These will form the chain that is used for x5c\n let match;\n do {\n match = certificatePattern.exec(certificateContents);\n if (match) {\n publicKeys.push(match[3]);\n }\n } while (match);\n\n if (publicKeys.length === 0) {\n throw new Error(\"The file at the specified path does not contain a PEM-encoded certificate.\");\n }\n\n const thumbprint = createHash(\"sha1\")\n .update(Buffer.from(publicKeys[0], \"base64\"))\n .digest(\"hex\")\n .toUpperCase();\n\n return {\n certificateContents,\n thumbprint,\n x5c,\n };\n }\n}\n"]}