Issue Title: Forward MSAL client metadata headers through IMDS to ESTS
Problem
IMDSv1 managed identity token requests only send Metadata: true. IMDS cannot forward client metadata to ESTS because the headers are never sent. This was fixed in MSAL.NET (PR #5912) and needs to be replicated across all MSAL SDKs.
What to do
Add three HTTP headers to every IMDSv1 managed identity token request:
| Header |
Value |
Purpose |
x-client-SKU |
SDK product name |
Identifies the MSAL SDK |
x-client-Ver |
SDK version string |
Identifies the SDK version |
x-ms-client-request-id |
Correlation GUID |
Traces the request end-to-end |
CRITICAL: Use x-ms-client-request-id — NOT client-request-id. IMDS requires the x-ms- prefixed header.
Reference implementation (.NET)
// ImdsManagedIdentitySource.cs — CreateRequestAsync()
request.Headers.Add("Metadata", "true");
request.Headers.Add("x-client-SKU", platformProxy.GetProductName()); // "MSAL.NetCore"
request.Headers.Add("x-client-Ver", MsalIdHelper.GetMsalVersion()); // e.g. "4.82.1"
request.Headers.Add("x-ms-client-request-id", correlationId.ToString());
Implementation per language
MSAL Python (AzureAD/microsoft-authentication-library-for-python)
Source file: msal/managed_identity.py
- Find
_obtain_token_on_azure_vm() where http_client.get(...) is called.
- Change
headers={"Metadata": "true"} to include the three new headers.
- SKU value:
"MSAL.Python" (from msal/sku.py → SKU)
- Version: from
msal/sku.py → __version__
- Correlation ID: generate
str(uuid.uuid4()) or use the existing correlation ID from the managed identity flow.
from .sku import SKU, __version__
import uuid
headers = {
"Metadata": "true",
"x-client-SKU": SKU,
"x-client-Ver": __version__,
"x-ms-client-request-id": str(uuid.uuid4()),
}
Test file: tests/test_mi.py
- Update existing IMDS test assertions to expect the new headers.
- Use
unittest.mock.ANY for version and correlation ID in assert_called_with.
- Additionally validate the correlation ID is a valid UUID.
- Test the SKU against a hardcoded constant
"MSAL.Python", not by importing SKU from the product.
EXPECTED_SKU = "MSAL.Python" # hardcoded constant, not imported from product
mock_get.assert_called_with(
"http://169.254.169.254/metadata/identity/oauth2/token",
params={"api-version": "2018-02-01", "resource": RESOURCE},
headers={
"Metadata": "true",
"x-client-SKU": EXPECTED_SKU,
"x-client-Ver": unittest.mock.ANY,
"x-ms-client-request-id": unittest.mock.ANY,
},
)
# Validate correlation ID is a valid UUID
corr_id = mock_get.call_args.kwargs["headers"]["x-ms-client-request-id"]
uuid.UUID(corr_id)
MSAL JavaScript (AzureAD/microsoft-authentication-library-for-js)
Source file: lib/msal-node/src/client/ManagedIdentitySources/Imds.ts
- Find
createRequest() where request.headers[ManagedIdentityHeaders.METADATA_HEADER_NAME] = "true" is set.
- Add the three new headers after it.
- SKU value:
"@azure/msal-node" (from lib/msal-node/src/packageMetadata.ts → name)
- Version: from
packageMetadata.ts → version
- Correlation ID: from the request context (check
ManagedIdentityApplication.ts which generates GUIDs via this.cryptoProvider.createNewGuid()).
import { name, version } from "../../packageMetadata.js";
request.headers[ManagedIdentityHeaders.METADATA_HEADER_NAME] = "true";
request.headers["x-client-SKU"] = name;
request.headers["x-client-Ver"] = version;
request.headers["x-ms-client-request-id"] = this.correlationId;
Test file: lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts
- Add header assertions to existing IMDS tests using the
sendGetRequestAsyncSpy.
- Assert SKU against hardcoded
"@azure/msal-node" constant.
- Validate correlation ID matches UUID regex.
const EXPECTED_SKU = "@azure/msal-node";
const headers = sendGetRequestAsyncSpy.mock.lastCall?.[1];
expect(headers["x-client-SKU"]).toBe(EXPECTED_SKU);
expect(headers["x-client-Ver"]).toBeDefined();
expect(headers["x-ms-client-request-id"]).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
);
MSAL Go (AzureAD/microsoft-authentication-library-for-go)
Source file: apps/managedidentity/managedidentity.go
- Find
createIMDSAuthRequest() where req.Header.Set(metaHTTPHeaderName, "true") is set.
- Add the three new headers after it.
- SKU value:
"MSAL.Go" (hardcoded in apps/internal/oauth/ops/internal/comm/comm.go)
- Version: from
apps/internal/version/version.go → version.Version
- Correlation ID: thread it through from the caller or generate via
uuid.New().String().
import (
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version"
"github.com/google/uuid"
)
req.Header.Set(metaHTTPHeaderName, "true")
req.Header.Set("x-client-SKU", "MSAL.Go")
req.Header.Set("x-client-Ver", version.Version)
req.Header.Set("x-ms-client-request-id", uuid.New().String())
Test file: apps/managedidentity/managedidentity_test.go
- Add header assertions inside the existing IMDS test callback using
WithCallback(func(req *http.Request){...}).
- Assert SKU against hardcoded
"MSAL.Go" constant.
const expectedSKU = "MSAL.Go"
mock.WithCallback(func(req *http.Request) {
// existing query param assertions...
if got := req.Header.Get("x-client-SKU"); got != expectedSKU {
t.Errorf("x-client-SKU = %q, want %q", got, expectedSKU)
}
if got := req.Header.Get("x-client-Ver"); got == "" {
t.Error("x-client-Ver header is empty")
}
corrID := req.Header.Get("x-ms-client-request-id")
if _, err := uuid.Parse(corrID); err != nil {
t.Errorf("x-ms-client-request-id not a valid UUID: %q", corrID)
}
})
MSAL Java (AzureAD/microsoft-authentication-library-for-java)
Source file: msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IMDSManagedIdentitySource.java
- Find where
managedIdentityRequest.headers.put("Metadata", "true") is set.
- Add the three new headers after it.
- SKU: use
HttpHeaders.PRODUCT_HEADER_NAME / HttpHeaders.PRODUCT_HEADER_VALUE ("MSAL.Java")
- Version: use
HttpHeaders.PRODUCT_VERSION_HEADER_NAME / HttpHeaders.PRODUCT_VERSION_HEADER_VALUE
- Correlation ID: from the request context.
managedIdentityRequest.headers.put("Metadata", "true");
managedIdentityRequest.headers.put(
HttpHeaders.PRODUCT_HEADER_NAME, HttpHeaders.PRODUCT_HEADER_VALUE);
managedIdentityRequest.headers.put(
HttpHeaders.PRODUCT_VERSION_HEADER_NAME, HttpHeaders.PRODUCT_VERSION_HEADER_VALUE);
managedIdentityRequest.headers.put(
"x-ms-client-request-id", requestContext.correlationId());
Note: HttpHeaders.CORRELATION_ID_HEADER_NAME is "client-request-id" — do not use it. Use the string "x-ms-client-request-id" directly, or add a new constant.
Test file: msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ManagedIdentityTests.java
- Add header assertions to existing IMDS tests using Mockito
ArgumentCaptor.
- Assert SKU against hardcoded
"MSAL.Java" constant.
private static final String EXPECTED_SKU = "MSAL.Java";
ArgumentCaptor<HttpRequest> captor = ArgumentCaptor.forClass(HttpRequest.class);
verify(httpClientMock).send(captor.capture());
HttpRequest req = captor.getValue();
assertEquals(EXPECTED_SKU, req.headers().get("x-client-SKU"));
assertNotNull(req.headers().get("x-client-Ver"));
assertDoesNotThrow(() -> UUID.fromString(req.headers().get("x-ms-client-request-id")));
Rules (from .NET PR review)
- Use
x-ms-client-request-id — not client-request-id for IMDS.
- Test SKU against a hardcoded string constant — never import the product value into tests.
- Validate correlation ID is a valid UUID in tests.
- Add assertions to existing IMDS tests — do not create new standalone test methods.
- Use existing SDK constants for header names and values where available.
- No changes to CloudShell, App Service, Azure Arc, or any other managed identity source — IMDS only.
Issue Title: Forward MSAL client metadata headers through IMDS to ESTS
Problem
IMDSv1 managed identity token requests only send
Metadata: true. IMDS cannot forward client metadata to ESTS because the headers are never sent. This was fixed in MSAL.NET (PR #5912) and needs to be replicated across all MSAL SDKs.What to do
Add three HTTP headers to every IMDSv1 managed identity token request:
x-client-SKUx-client-Verx-ms-client-request-idCRITICAL: Use
x-ms-client-request-id— NOTclient-request-id. IMDS requires thex-ms-prefixed header.Reference implementation (.NET)
Implementation per language
MSAL Python (
AzureAD/microsoft-authentication-library-for-python)Source file:
msal/managed_identity.py_obtain_token_on_azure_vm()wherehttp_client.get(...)is called.headers={"Metadata": "true"}to include the three new headers."MSAL.Python"(frommsal/sku.py→SKU)msal/sku.py→__version__str(uuid.uuid4())or use the existing correlation ID from the managed identity flow.Test file:
tests/test_mi.pyunittest.mock.ANYfor version and correlation ID inassert_called_with."MSAL.Python", not by importingSKUfrom the product.MSAL JavaScript (
AzureAD/microsoft-authentication-library-for-js)Source file:
lib/msal-node/src/client/ManagedIdentitySources/Imds.tscreateRequest()whererequest.headers[ManagedIdentityHeaders.METADATA_HEADER_NAME] = "true"is set."@azure/msal-node"(fromlib/msal-node/src/packageMetadata.ts→name)packageMetadata.ts→versionManagedIdentityApplication.tswhich generates GUIDs viathis.cryptoProvider.createNewGuid()).Test file:
lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.tssendGetRequestAsyncSpy."@azure/msal-node"constant.MSAL Go (
AzureAD/microsoft-authentication-library-for-go)Source file:
apps/managedidentity/managedidentity.gocreateIMDSAuthRequest()wherereq.Header.Set(metaHTTPHeaderName, "true")is set."MSAL.Go"(hardcoded inapps/internal/oauth/ops/internal/comm/comm.go)apps/internal/version/version.go→version.Versionuuid.New().String().Test file:
apps/managedidentity/managedidentity_test.goWithCallback(func(req *http.Request){...})."MSAL.Go"constant.MSAL Java (
AzureAD/microsoft-authentication-library-for-java)Source file:
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IMDSManagedIdentitySource.javamanagedIdentityRequest.headers.put("Metadata", "true")is set.HttpHeaders.PRODUCT_HEADER_NAME/HttpHeaders.PRODUCT_HEADER_VALUE("MSAL.Java")HttpHeaders.PRODUCT_VERSION_HEADER_NAME/HttpHeaders.PRODUCT_VERSION_HEADER_VALUENote:
HttpHeaders.CORRELATION_ID_HEADER_NAMEis"client-request-id"— do not use it. Use the string"x-ms-client-request-id"directly, or add a new constant.Test file:
msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ManagedIdentityTests.javaArgumentCaptor."MSAL.Java"constant.Rules (from .NET PR review)
x-ms-client-request-id— notclient-request-idfor IMDS.