Skip to content

Commit 64b3278

Browse files
CopilotgladjohnCopilotCopilot
authored
Add MSAL client metadata headers to IMDS managed identity requests (#8529)
IMDS managed identity token requests only sent `Metadata: true`, preventing IMDS from forwarding client telemetry to ESTS. Aligns with the fix already shipped in MSAL.NET ([PR #5912](AzureAD/microsoft-authentication-library-for-dotnet#5912)). ### Changes - **`Constants.ts`** — Added `CLIENT_SKU`, `CLIENT_VER`, and `CLIENT_REQUEST_ID` entries to `ManagedIdentityHeaders` so header strings are defined as named constants - **`Imds.ts`** — Added `x-client-SKU`, `x-client-VER`, and `x-ms-client-request-id` headers to `createRequest()` using `ManagedIdentityHeaders` constants, `Constants.MSAL_SKU`, `packageMetadata.version`, and `cryptoProvider.createNewGuid()` - **`BaseManagedIdentitySource.ts`** — Changed `cryptoProvider` visibility from `private` to `protected` so `Imds` can generate correlation GUIDs - **`Imds.spec.ts`** — Added header assertions to three existing tests (UAMI Client Id, UAMI Resource Id, SAMI) using `ManagedIdentityHeaders` and `NodeConstants.MSAL_SKU` constants with UUID regex validation - **`managed-identity.md`** — Updated Troubleshooting section to reflect that IMDS requests now include `x-ms-client-request-id` for end-to-end tracing ```ts request.headers[ManagedIdentityHeaders.METADATA_HEADER_NAME] = "true"; request.headers[ManagedIdentityHeaders.CLIENT_SKU] = NodeConstants.MSAL_SKU; request.headers[ManagedIdentityHeaders.CLIENT_VER] = packageVersion; request.headers[ManagedIdentityHeaders.CLIENT_REQUEST_ID] = this.cryptoProvider.createNewGuid(); ``` > **Note:** Uses `x-ms-client-request-id` — not `client-request-id`. IMDS requires the `x-ms-` prefix. > **Note:** Uses `Constants.MSAL_SKU` (`"msal.js.node"`) for `x-client-SKU` to align with existing msal-node AAD telemetry, and canonical `x-client-VER` casing matching `AADServerParamKeys.X_CLIENT_VER`. No changes to CloudShell, App Service, Azure Arc, Service Fabric, or any other managed identity source. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: gladjohn <90415114+gladjohn@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 45bb72c commit 64b3278

7 files changed

Lines changed: 76 additions & 42 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Add MSAL client metadata headers (x-client-SKU, x-client-VER, x-ms-client-request-id) to IMDS managed identity requests [#8529](https://github.com/AzureAD/microsoft-authentication-library-for-js/pull/8529)",
4+
"packageName": "@azure/msal-node",
5+
"email": "nicagra@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

lib/msal-node/docs/managed-identity.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ MSALJS caches tokens from managed identity in memory. There is no eviction, but
110110

111111
## Troubleshooting
112112

113-
For failed requests the error response contains a correlation ID that can be used for further diagnostics and log analysis. Keep in mind that the correlation IDs generated in MSALJS or passed into MSAL are different than the one returned in server error responses, as MSALJS cannot pass the correlation ID to managed identity token acquisition endpoints.
113+
For failed requests the error response contains a correlation ID that can be used for further diagnostics and log analysis. Note that for IMDS requests, MSALJS sends an `x-ms-client-request-id` header that IMDS can forward to ESTS, enabling end-to-end request tracing. For other managed identity sources, the correlation IDs generated in MSALJS or passed into MSAL may differ from the one returned in server error responses.
114114

115115
### Potential errors
116116

lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ export abstract class BaseManagedIdentitySource {
9090
this.disableInternalRetries = disableInternalRetries;
9191
}
9292

93+
/**
94+
* Generates a new correlation ID for request tracing.
95+
*
96+
* @returns A new GUID string for use as a correlation or request ID
97+
*/
98+
protected createCorrelationId(): string {
99+
return this.cryptoProvider.createNewGuid();
100+
}
101+
93102
/**
94103
* Creates a managed identity request with source-specific parameters.
95104
* This method must be implemented by concrete managed identity sources to define

lib/msal-node/src/client/ManagedIdentitySources/Imds.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ManagedIdentityRequestParameters } from "../../config/ManagedIdentityRe
99
import { BaseManagedIdentitySource } from "./BaseManagedIdentitySource.js";
1010
import { CryptoProvider } from "../../crypto/CryptoProvider.js";
1111
import {
12+
Constants as NodeConstants,
1213
HttpMethod,
1314
ManagedIdentityEnvironmentVariableNames,
1415
ManagedIdentityHeaders,
@@ -18,6 +19,7 @@ import {
1819
} from "../../utils/Constants.js";
1920
import { NodeStorage } from "../../cache/NodeStorage.js";
2021
import { ImdsRetryPolicy } from "../../retry/ImdsRetryPolicy.js";
22+
import { version as packageVersion } from "../../packageMetadata.js";
2123

2224
// Documentation for IMDS is available at https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
2325

@@ -167,6 +169,11 @@ export class Imds extends BaseManagedIdentitySource {
167169
);
168170

169171
request.headers[ManagedIdentityHeaders.METADATA_HEADER_NAME] = "true";
172+
request.headers[ManagedIdentityHeaders.CLIENT_SKU] =
173+
NodeConstants.MSAL_SKU;
174+
request.headers[ManagedIdentityHeaders.CLIENT_VER] = packageVersion;
175+
request.headers[ManagedIdentityHeaders.CLIENT_REQUEST_ID] =
176+
this.createCorrelationId();
170177

171178
request.queryParameters[ManagedIdentityQueryParameters.API_VERSION] =
172179
IMDS_API_VERSION;

lib/msal-node/src/utils/Constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License.
44
*/
55

6+
import { AADServerParamKeys } from "@azure/msal-common/node";
67
import { DefaultManagedIdentityRetryPolicy } from "../retry/DefaultManagedIdentityRetryPolicy.js";
78
import { ImdsRetryPolicy } from "../retry/ImdsRetryPolicy.js";
89

@@ -19,6 +20,9 @@ export const ManagedIdentityHeaders = {
1920
METADATA_HEADER_NAME: "Metadata",
2021
APP_SERVICE_SECRET_HEADER_NAME: "X-IDENTITY-HEADER",
2122
ML_AND_SF_SECRET_HEADER_NAME: "secret",
23+
CLIENT_SKU: AADServerParamKeys.X_CLIENT_SKU,
24+
CLIENT_VER: AADServerParamKeys.X_CLIENT_VER,
25+
CLIENT_REQUEST_ID: "x-ms-client-request-id",
2226
} as const;
2327
export type ManagedIdentityHeaders =
2428
(typeof ManagedIdentityHeaders)[keyof typeof ManagedIdentityHeaders];

lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ import {
3636
} from "../../test_kit/ManagedIdentityTestUtils.js";
3737
import {
3838
DEFAULT_MANAGED_IDENTITY_ID,
39+
ManagedIdentityHeaders,
3940
ManagedIdentityQueryParameters,
4041
ManagedIdentitySourceNames,
42+
Constants as NodeConstants,
4143
} from "../../../src/utils/Constants.js";
4244
import {
4345
AccessTokenEntity,
@@ -62,6 +64,11 @@ import { NodeStorage } from "../../../src/cache/NodeStorage.js";
6264
import { CacheKVStore } from "../../../src/cache/serializer/SerializerTypes.js";
6365
import { ManagedIdentityUserAssignedIdQueryParameterNames } from "../../../src/client/ManagedIdentitySources/BaseManagedIdentitySource.js";
6466
import { ImdsRetryPolicy } from "../../../src/retry/ImdsRetryPolicy.js";
67+
import { version as packageVersion } from "../../../src/packageMetadata.js";
68+
69+
const EXPECTED_SKU = NodeConstants.MSAL_SKU;
70+
const UUID_REGEX =
71+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6572

6673
describe("Acquires a token successfully via an IMDS Managed Identity", () => {
6774
// IMDS doesn't need environment variables because there is a default IMDS endpoint
@@ -115,6 +122,17 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => {
115122
ManagedIdentityUserAssignedIdQueryParameterNames.MANAGED_IDENTITY_CLIENT_ID_2017
116123
)
117124
).toBe(false);
125+
126+
const headers = sendGetRequestAsyncSpy.mock.lastCall[1].headers;
127+
expect(headers[ManagedIdentityHeaders.CLIENT_SKU]).toBe(
128+
EXPECTED_SKU
129+
);
130+
expect(headers[ManagedIdentityHeaders.CLIENT_VER]).toBe(
131+
packageVersion
132+
);
133+
expect(headers[ManagedIdentityHeaders.CLIENT_REQUEST_ID]).toMatch(
134+
UUID_REGEX
135+
);
118136
});
119137

120138
test("acquires a User Assigned Object Id token", async () => {
@@ -169,6 +187,17 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => {
169187
)
170188
).toEqual(MANAGED_IDENTITY_RESOURCE_ID);
171189

190+
const headers = sendGetRequestAsyncSpy.mock.lastCall[1].headers;
191+
expect(headers[ManagedIdentityHeaders.CLIENT_SKU]).toBe(
192+
EXPECTED_SKU
193+
);
194+
expect(headers[ManagedIdentityHeaders.CLIENT_VER]).toBe(
195+
packageVersion
196+
);
197+
expect(headers[ManagedIdentityHeaders.CLIENT_REQUEST_ID]).toMatch(
198+
UUID_REGEX
199+
);
200+
172201
jest.restoreAllMocks();
173202
});
174203
});
@@ -185,6 +214,11 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => {
185214
});
186215

187216
test("acquires a token", async () => {
217+
const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn(
218+
networkClient,
219+
<any>"sendGetRequestAsync"
220+
);
221+
188222
const networkManagedIdentityResult: AuthenticationResult =
189223
await managedIdentityApplication.acquireToken(
190224
managedIdentityRequestParams
@@ -194,6 +228,17 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => {
194228
expect(networkManagedIdentityResult.accessToken).toEqual(
195229
DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken
196230
);
231+
232+
const headers = sendGetRequestAsyncSpy.mock.lastCall[1].headers;
233+
expect(headers[ManagedIdentityHeaders.CLIENT_SKU]).toBe(
234+
EXPECTED_SKU
235+
);
236+
expect(headers[ManagedIdentityHeaders.CLIENT_VER]).toBe(
237+
packageVersion
238+
);
239+
expect(headers[ManagedIdentityHeaders.CLIENT_REQUEST_ID]).toMatch(
240+
UUID_REGEX
241+
);
197242
});
198243

199244
test("returns an already acquired token from the cache", async () => {

0 commit comments

Comments
 (0)