- com.microsoft.azure:msal4j:[1.23.1]
+ com.microsoft.azure:msal4j:[1.26.0]
com.microsoft.azure:msal4j-persistence-extension:[1.3.0]
diff --git a/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ChainedTokenCredential.java b/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ChainedTokenCredential.java
index b3e2aaa1a4dea..9fa63faf1db17 100644
--- a/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ChainedTokenCredential.java
+++ b/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ChainedTokenCredential.java
@@ -81,7 +81,7 @@ public AccessToken getToken(TokenRequestContext request) {
try {
AccessToken accessToken = credential.getToken(request);
LOGGER.atInfo()
- .addKeyValue("credentialType", selectedCredential.get().getClass().getCanonicalName())
+ .addKeyValue("credentialType", credential.getClass().getCanonicalName())
.log("Azure Identity => Attempted credential returns a token.");
selectedCredential.set(credential);
return accessToken;
diff --git a/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ManagedIdentityCredential.java b/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ManagedIdentityCredential.java
index 95cc74234934a..c0a0340dbfcac 100644
--- a/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ManagedIdentityCredential.java
+++ b/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ManagedIdentityCredential.java
@@ -45,6 +45,11 @@
* Sets
*
*
+ * On Azure Arc-enabled servers, this credential supports both system-assigned and user-assigned managed
+ * identities. Use {@link ManagedIdentityCredentialBuilder#clientId(String)},
+ * {@link ManagedIdentityCredentialBuilder#resourceId(String)}, or
+ * {@link ManagedIdentityCredentialBuilder#objectId(String)} to select a user-assigned identity.
+ *
* Sample: Construct a simple ManagedIdentityCredential
*
* The following code sample demonstrates the creation of a ManagedIdentityCredential,
@@ -97,8 +102,7 @@ public AccessToken getToken(TokenRequestContext request) {
if (!CoreUtils.isNullOrEmpty(managedIdentityId)) {
ManagedIdentitySourceType managedIdentitySourceType = ManagedIdentityApplication.getManagedIdentitySource();
- if (ManagedIdentitySourceType.CLOUD_SHELL.equals(managedIdentitySourceType)
- || ManagedIdentitySourceType.AZURE_ARC.equals(managedIdentitySourceType)) {
+ if (ManagedIdentitySourceType.CLOUD_SHELL.equals(managedIdentitySourceType)) {
throw LOGGER.throwableAtError()
.log("ManagedIdentityCredential authentication unavailable. "
+ "User-assigned managed identity is not supported in " + managedIdentitySourceType
diff --git a/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ManagedIdentityCredentialBuilder.java b/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ManagedIdentityCredentialBuilder.java
index b4e3a827f7683..afea0f8c308ba 100644
--- a/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ManagedIdentityCredentialBuilder.java
+++ b/sdk/identity-v2/azure-identity/src/main/java/com/azure/v2/identity/ManagedIdentityCredentialBuilder.java
@@ -26,6 +26,9 @@
* managed identity authentication
* documentation.
*
+ * Azure Arc-enabled servers support user-assigned managed identities selected by client ID, resource ID, or object
+ * ID.
+ *
* Sample: Construct a simple ManagedIdentityCredential
*
* The following code sample demonstrates the creation of a {@link ManagedIdentityCredential},
diff --git a/sdk/identity-v2/azure-identity/src/test/java/com/azure/v2/identity/DefaultAzureCredentialTest.java b/sdk/identity-v2/azure-identity/src/test/java/com/azure/v2/identity/DefaultAzureCredentialTest.java
new file mode 100644
index 0000000000000..712815c341d30
--- /dev/null
+++ b/sdk/identity-v2/azure-identity/src/test/java/com/azure/v2/identity/DefaultAzureCredentialTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.v2.identity;
+
+import com.azure.v2.core.credentials.TokenRequestContext;
+import com.azure.v2.identity.implementation.client.ManagedIdentityClient;
+import com.azure.v2.identity.implementation.models.ManagedIdentityClientOptions;
+import com.azure.v2.identity.util.TestConfigurationSource;
+import com.azure.v2.identity.util.TestUtils;
+import com.microsoft.aad.msal4j.ManagedIdentityApplication;
+import com.microsoft.aad.msal4j.ManagedIdentitySourceType;
+import io.clientcore.core.credentials.oauth.AccessToken;
+import io.clientcore.core.utils.configuration.Configuration;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.UUID;
+
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DefaultAzureCredentialTest {
+ private static final String CLIENT_ID = UUID.randomUUID().toString();
+ private static final String RESOURCE_ID = "/subscriptions/" + UUID.randomUUID()
+ + "/resourcegroups/aresourcegroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ident";
+
+ @ParameterizedTest
+ @ValueSource(strings = { "clientId", "resourceId" })
+ public void testUseArcUserAssignedManagedIdentityCredential(String identityType) {
+ // setup
+ String token = "token";
+ TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com");
+ OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1);
+ Configuration configuration = TestUtils.createTestConfiguration(new TestConfigurationSource());
+
+ String clientId = "clientId".equals(identityType) ? CLIENT_ID : null;
+ String resourceId = "resourceId".equals(identityType) ? RESOURCE_ID : null;
+
+ // mock
+ try (MockedStatic applicationMock = mockStatic(ManagedIdentityApplication.class);
+ MockedConstruction managedIdentityMock
+ = mockConstruction(ManagedIdentityClient.class, (miClient, context) -> {
+ ManagedIdentityClientOptions options = (ManagedIdentityClientOptions) context.arguments().get(0);
+ Assertions.assertEquals(clientId, options.getClientId());
+ Assertions.assertEquals(resourceId, options.getResourceId());
+ when(miClient.authenticate(request)).thenReturn(TestUtils.getMockAccessToken(token, expiresAt));
+ })) {
+ applicationMock.when(ManagedIdentityApplication::getManagedIdentitySource)
+ .thenReturn(ManagedIdentitySourceType.AZURE_ARC);
+
+ DefaultAzureCredentialBuilder builder = new DefaultAzureCredentialBuilder().configuration(configuration);
+ if (clientId != null) {
+ builder.managedIdentityClientId(clientId);
+ } else {
+ builder.managedIdentityResourceId(resourceId);
+ }
+ DefaultAzureCredential credential = builder.build();
+
+ // test
+ AccessToken firstToken = credential.getToken(request);
+ AccessToken tokenFromCachedCredential = credential.getToken(request);
+ Assertions.assertEquals(token, firstToken.getToken());
+ Assertions.assertEquals(token, tokenFromCachedCredential.getToken());
+ Assertions.assertEquals(expiresAt.getSecond(), firstToken.getExpiresAt().getSecond());
+ Assertions.assertEquals(1, managedIdentityMock.constructed().size());
+ verify(managedIdentityMock.constructed().get(0), times(2)).authenticate(request);
+ }
+ }
+}
diff --git a/sdk/identity-v2/azure-identity/src/test/java/com/azure/v2/identity/ManagedIdentityCredentialTest.java b/sdk/identity-v2/azure-identity/src/test/java/com/azure/v2/identity/ManagedIdentityCredentialTest.java
index 6af4251d3c390..b9d58a3e906f6 100644
--- a/sdk/identity-v2/azure-identity/src/test/java/com/azure/v2/identity/ManagedIdentityCredentialTest.java
+++ b/sdk/identity-v2/azure-identity/src/test/java/com/azure/v2/identity/ManagedIdentityCredentialTest.java
@@ -3,26 +3,37 @@
package com.azure.v2.identity;
+import com.azure.v2.identity.exceptions.CredentialUnavailableException;
import com.azure.v2.identity.implementation.client.ManagedIdentityClient;
+import com.azure.v2.identity.implementation.models.ManagedIdentityClientOptions;
import com.azure.v2.identity.util.TestConfigurationSource;
import com.azure.v2.identity.util.TestUtils;
import com.azure.v2.core.credentials.TokenRequestContext;
+import com.microsoft.aad.msal4j.ManagedIdentityApplication;
+import com.microsoft.aad.msal4j.ManagedIdentitySourceType;
import io.clientcore.core.credentials.oauth.AccessToken;
import io.clientcore.core.utils.configuration.Configuration;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.UUID;
import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class ManagedIdentityCredentialTest {
private static final String CLIENT_ID = UUID.randomUUID().toString();
+ private static final String OBJECT_ID = UUID.randomUUID().toString();
+ private static final String RESOURCE_ID = "/subscriptions/" + UUID.randomUUID()
+ + "/resourcegroups/aresourcegroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ident";
@Test
public void testMiAuthFlow() {
@@ -52,4 +63,68 @@ public void testMiAuthFlow() {
Assertions.assertNotNull(managedIdentityMock);
}
}
+
+ @ParameterizedTest
+ @ValueSource(strings = { "clientId", "resourceId", "objectId" })
+ public void testArcUserAssigned(String identityType) {
+ // setup
+ String token = "token";
+ TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com");
+ OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1);
+ Configuration configuration = TestUtils.createTestConfiguration(new TestConfigurationSource());
+
+ String clientId = "clientId".equals(identityType) ? CLIENT_ID : null;
+ String resourceId = "resourceId".equals(identityType) ? RESOURCE_ID : null;
+ String objectId = "objectId".equals(identityType) ? OBJECT_ID : null;
+
+ // mock
+ try (MockedStatic applicationMock = mockStatic(ManagedIdentityApplication.class);
+ MockedConstruction managedIdentityMock
+ = mockConstruction(ManagedIdentityClient.class, (miClient, context) -> {
+ ManagedIdentityClientOptions options = (ManagedIdentityClientOptions) context.arguments().get(0);
+ Assertions.assertEquals(clientId, options.getClientId());
+ Assertions.assertEquals(resourceId, options.getResourceId());
+ Assertions.assertEquals(objectId, options.getObjectId());
+ when(miClient.authenticate(request)).thenReturn(TestUtils.getMockAccessToken(token, expiresAt));
+ })) {
+ applicationMock.when(ManagedIdentityApplication::getManagedIdentitySource)
+ .thenReturn(ManagedIdentitySourceType.AZURE_ARC);
+
+ ManagedIdentityCredentialBuilder builder
+ = new ManagedIdentityCredentialBuilder().configuration(configuration);
+ if (clientId != null) {
+ builder.clientId(clientId);
+ } else if (resourceId != null) {
+ builder.resourceId(resourceId);
+ } else {
+ builder.objectId(objectId);
+ }
+
+ // test
+ AccessToken accessToken = builder.build().getToken(request);
+ Assertions.assertEquals(token, accessToken.getToken());
+ Assertions.assertEquals(expiresAt.getSecond(), accessToken.getExpiresAt().getSecond());
+ Assertions.assertEquals(1, managedIdentityMock.constructed().size());
+ }
+ }
+
+ @Test
+ public void testCloudShellUserAssigned() {
+ // setup
+ TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com");
+ Configuration configuration = TestUtils.createTestConfiguration(new TestConfigurationSource());
+
+ try (MockedStatic applicationMock = mockStatic(ManagedIdentityApplication.class)) {
+ applicationMock.when(ManagedIdentityApplication::getManagedIdentitySource)
+ .thenReturn(ManagedIdentitySourceType.CLOUD_SHELL);
+
+ // test
+ ManagedIdentityCredential credential
+ = new ManagedIdentityCredentialBuilder().configuration(configuration).objectId(OBJECT_ID).build();
+ CredentialUnavailableException exception
+ = Assertions.assertThrows(CredentialUnavailableException.class, () -> credential.getToken(request));
+ Assertions.assertTrue(
+ exception.getMessage().contains("User-assigned managed identity is not supported in CLOUD_SHELL"));
+ }
+ }
}
diff --git a/sdk/identity/azure-identity-broker/pom.xml b/sdk/identity/azure-identity-broker/pom.xml
index dde164700249f..b85196ac00fb8 100644
--- a/sdk/identity/azure-identity-broker/pom.xml
+++ b/sdk/identity/azure-identity-broker/pom.xml
@@ -42,7 +42,7 @@
com.microsoft.azure
msal4j
- 1.23.1
+ 1.26.0
com.microsoft.azure
@@ -67,7 +67,7 @@
- com.microsoft.azure:msal4j:[1.23.1]
+ com.microsoft.azure:msal4j:[1.26.0]
com.microsoft.azure:msal4j-brokers:[1.0.0]
diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md
index 404eb32d5b65a..0e382f8104947 100644
--- a/sdk/identity/azure-identity/CHANGELOG.md
+++ b/sdk/identity/azure-identity/CHANGELOG.md
@@ -4,6 +4,8 @@
### Features Added
+- Added support for user-assigned managed identities on Azure Arc in `ManagedIdentityCredential` and `DefaultAzureCredential`.
+
### Breaking Changes
### Bugs Fixed
@@ -14,6 +16,10 @@
- Improved `AzureDeveloperCliCredential` error handling to extract meaningful messages from `azd auth token` JSON output, providing cleaner error messages to users.
+#### Dependency Updates
+
+- Upgraded `msal4j` from `1.23.1` to `1.26.0`.
+
## 1.18.5 (2026-08-24)
### Other Changes
diff --git a/sdk/identity/azure-identity/README.md b/sdk/identity/azure-identity/README.md
index 1fe34673fee88..40a5114a066a7 100644
--- a/sdk/identity/azure-identity/README.md
+++ b/sdk/identity/azure-identity/README.md
@@ -158,6 +158,10 @@ The [Managed identity authentication](https://learn.microsoft.com/entra/identity
- [Azure Virtual Machines](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-to-use-vm-token)
- [Azure Virtual Machines Scale Sets](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-powershell-windows-vmss)
+Azure Arc supports system-assigned and user-assigned managed identities. `ManagedIdentityCredential` can select a
+user-assigned identity by client ID, resource ID, or object ID. `DefaultAzureCredential` supports client ID and
+resource ID.
+
**Note:** Use `azure-identity` version `1.7.0` or later to utilize [token caching](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/identity/azure-identity/TOKEN_CACHING.md) support for managed identity authentication.
## Cloud / Sovereign configuration
diff --git a/sdk/identity/azure-identity/pom.xml b/sdk/identity/azure-identity/pom.xml
index c16935f8f74f0..d5df65adbd42c 100644
--- a/sdk/identity/azure-identity/pom.xml
+++ b/sdk/identity/azure-identity/pom.xml
@@ -52,7 +52,7 @@
com.microsoft.azure
msal4j
- 1.23.1
+ 1.26.0
com.microsoft.azure
@@ -109,7 +109,7 @@
- com.microsoft.azure:msal4j:[1.23.1]
+ com.microsoft.azure:msal4j:[1.26.0]
com.microsoft.azure:msal4j-persistence-extension:[1.3.0]
net.java.dev.jna:jna-platform:[5.17.0]
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ArcIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ArcIdentityCredential.java
index eb61c02d5fc92..16896bdf54c63 100644
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ArcIdentityCredential.java
+++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ArcIdentityCredential.java
@@ -6,7 +6,6 @@
import com.azure.core.annotation.Immutable;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenRequestContext;
-import com.azure.core.exception.ClientAuthenticationException;
import com.azure.core.util.Configuration;
import com.azure.core.util.logging.ClientLogger;
import com.azure.identity.implementation.IdentityClient;
@@ -43,13 +42,6 @@ class ArcIdentityCredential extends ManagedIdentityServiceCredential {
* @return A publisher that emits an {@link AccessToken}.
*/
public Mono authenticate(TokenRequestContext request) {
- if (getClientId() != null) {
- return Mono.error(LOGGER.logExceptionAsError(new ClientAuthenticationException(
- "User assigned identity is not supported by the Azure Arc Managed Identity Endpoint. To authenticate "
- + "with the system assigned identity omit the client id when constructing the"
- + " ManagedIdentityCredential.",
- null)));
- }
return identityClient.authenticateWithManagedIdentityMsalClient(request);
}
}
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java
index b1f5e22ba49e6..3fd77317cbbc0 100644
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java
+++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java
@@ -46,6 +46,11 @@
* Sets
*
*
+ * On Azure Arc-enabled servers, this credential supports both system-assigned and user-assigned managed
+ * identities. Use {@link ManagedIdentityCredentialBuilder#clientId(String)},
+ * {@link ManagedIdentityCredentialBuilder#resourceId(String)}, or
+ * {@link ManagedIdentityCredentialBuilder#objectId(String)} to select a user-assigned identity.
+ *
* Sample: Construct a simple ManagedIdentityCredential
*
* The following code sample demonstrates the creation of a ManagedIdentityCredential,
@@ -155,8 +160,7 @@ public Mono getToken(TokenRequestContext request) {
// Check a couple cases that are not supported for user-assigned managed identity.
if (!CoreUtils.isNullOrEmpty(managedIdentityId)) {
ManagedIdentitySourceType managedIdentitySourceType = ManagedIdentityApplication.getManagedIdentitySource();
- if (ManagedIdentitySourceType.CLOUD_SHELL.equals(managedIdentitySourceType)
- || ManagedIdentitySourceType.AZURE_ARC.equals(managedIdentitySourceType)) {
+ if (ManagedIdentitySourceType.CLOUD_SHELL.equals(managedIdentitySourceType)) {
return Mono.error(LoggingUtil.logCredentialUnavailableException(LOGGER, identityClientOptions,
new CredentialUnavailableException("ManagedIdentityCredential authentication unavailable. "
+ "User-assigned managed identity is not supported in " + managedIdentitySourceType
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredentialBuilder.java
index a55cafdba5b4c..cd1d479b19166 100644
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredentialBuilder.java
+++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredentialBuilder.java
@@ -23,6 +23,9 @@
* managed identity authentication
* documentation.
*
+ * Azure Arc-enabled servers support user-assigned managed identities selected by client ID, resource ID, or object
+ * ID.
+ *
* Sample: Construct a simple ManagedIdentityCredential
*
* The following code sample demonstrates the creation of a {@link ManagedIdentityCredential},
diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java
index 7859aa920d0bf..51c1684609264 100644
--- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java
+++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java
@@ -13,12 +13,15 @@
import com.azure.identity.implementation.IdentityClient;
import com.azure.identity.util.EmptyEnvironmentConfigurationSource;
import com.azure.identity.util.TestUtils;
+import com.microsoft.aad.msal4j.ManagedIdentityApplication;
+import com.microsoft.aad.msal4j.ManagedIdentitySourceType;
import com.microsoft.aad.msal4j.MsalServiceException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -37,12 +40,18 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class DefaultAzureCredentialTest {
private static final String TENANT_ID = "contoso.com";
private static final String CLIENT_ID = UUID.randomUUID().toString();
+ private static final String RESOURCE_ID = "/subscriptions/" + UUID.randomUUID()
+ + "/resourcegroups/aresourcegroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ident";
+ private static final int IDENTITY_CLIENT_CLIENT_ID_ARGUMENT_INDEX = 1;
+ private static final int IDENTITY_CLIENT_RESOURCE_ID_ARGUMENT_INDEX = 5;
+ private static final int IDENTITY_CLIENT_OBJECT_ID_ARGUMENT_INDEX = 6;
@Test
public void testUseEnvironmentCredential() {
@@ -113,6 +122,47 @@ public void testUseManagedIdentityCredential() {
}
}
+ @ParameterizedTest
+ @ValueSource(strings = { "clientId", "resourceId" })
+ public void testUseArcUserAssignedManagedIdentityCredential(String identityType) {
+ // setup
+ String token = "token";
+ TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com");
+ OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1);
+ Configuration configuration = TestUtils.createTestConfiguration(
+ new TestConfigurationSource().put("AZURE_TOKEN_CREDENTIALS", "ManagedIdentityCredential"));
+ String clientId = "clientId".equals(identityType) ? CLIENT_ID : null;
+ String resourceId = "resourceId".equals(identityType) ? RESOURCE_ID : null;
+
+ // mock
+ try (MockedStatic applicationMock = mockStatic(ManagedIdentityApplication.class);
+ MockedConstruction mocked
+ = mockConstruction(IdentityClient.class, (identityClient, context) -> {
+ assertEquals(clientId, context.arguments().get(IDENTITY_CLIENT_CLIENT_ID_ARGUMENT_INDEX));
+ assertEquals(resourceId, context.arguments().get(IDENTITY_CLIENT_RESOURCE_ID_ARGUMENT_INDEX));
+ Assertions.assertNull(context.arguments().get(IDENTITY_CLIENT_OBJECT_ID_ARGUMENT_INDEX));
+ when(identityClient.authenticateWithManagedIdentityMsalClient(request))
+ .thenReturn(TestUtils.getMockAccessToken(token, expiresAt));
+ })) {
+ applicationMock.when(ManagedIdentityApplication::getManagedIdentitySource)
+ .thenReturn(ManagedIdentitySourceType.AZURE_ARC);
+
+ DefaultAzureCredentialBuilder builder = new DefaultAzureCredentialBuilder().configuration(configuration);
+ if (clientId != null) {
+ builder.managedIdentityClientId(clientId);
+ } else {
+ builder.managedIdentityResourceId(resourceId);
+ }
+
+ // test
+ StepVerifier.create(builder.build().getToken(request))
+ .expectNextMatches(accessToken -> token.equals(accessToken.getToken())
+ && expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
+ .verifyComplete();
+ assertEquals(1, mocked.constructed().size());
+ }
+ }
+
@Test
public void testUseWorkloadIdentityCredentialWithManagedIdentityClientId() {
// setup
@@ -450,14 +500,10 @@ public void testCredentialUnavailableSync() {
@Test
public void testInvalidIdCombination() {
- // setup
- String resourceId = "/subscriptions/" + UUID.randomUUID()
- + "/resourcegroups/aresourcegroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ident";
-
// test
Assertions.assertThrows(IllegalStateException.class,
() -> new DefaultAzureCredentialBuilder().managedIdentityClientId(CLIENT_ID)
- .managedIdentityResourceId(resourceId)
+ .managedIdentityResourceId(RESOURCE_ID)
.build());
}
diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java
index 00e94d573990a..a7f4cf4ed265c 100644
--- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java
+++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java
@@ -12,11 +12,14 @@
import com.azure.identity.implementation.IdentityClient;
import com.azure.identity.implementation.IdentityClientOptions;
import com.azure.identity.util.TestUtils;
+import com.microsoft.aad.msal4j.ManagedIdentityApplication;
+import com.microsoft.aad.msal4j.ManagedIdentitySourceType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
import reactor.test.StepVerifier;
import java.nio.charset.StandardCharsets;
@@ -28,12 +31,15 @@
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class ManagedIdentityCredentialTest {
private static final String CLIENT_ID = UUID.randomUUID().toString();
private static final String OBJECT_ID = UUID.randomUUID().toString();
+ private static final String RESOURCE_ID = "/subscriptions/" + UUID.randomUUID()
+ + "/resourcegroups/aresourcegroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ident";
@Test
public void testVirtualMachineMSICredentialConfigurations() {
@@ -128,52 +134,103 @@ public void testIMDS() {
}
}
- @Test
- public void testArcUserAssigned() {
+ @ParameterizedTest
+ @ValueSource(strings = { "clientId", "resourceId", "objectId" })
+ public void testArcUserAssigned(String identityType) {
// setup
- String endpoint = "http://localhost";
+ String token = "token";
TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com");
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put("IDENTITY_ENDPOINT", endpoint).put("IMDS_ENDPOINT", endpoint));
+ OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1);
+ Configuration configuration = TestUtils.createTestConfiguration(new TestConfigurationSource());
- // test
- ManagedIdentityCredential credential
- = new ManagedIdentityCredentialBuilder().configuration(configuration).clientId(CLIENT_ID).build();
- StepVerifier.create(credential.getToken(request))
- .expectErrorMatches(t -> t instanceof ClientAuthenticationException)
- .verify();
+ String clientId = "clientId".equals(identityType) ? CLIENT_ID : null;
+ String resourceId = "resourceId".equals(identityType) ? RESOURCE_ID : null;
+ String objectId = "objectId".equals(identityType) ? OBJECT_ID : null;
+
+ // mock
+ try (MockedStatic applicationMock = mockStatic(ManagedIdentityApplication.class);
+ MockedConstruction identityClientMock
+ = mockConstruction(IdentityClient.class, (identityClient, context) -> {
+ assertEquals(clientId, context.arguments().get(1));
+ assertEquals(resourceId, context.arguments().get(5));
+ assertEquals(objectId, context.arguments().get(6));
+ when(identityClient.authenticateWithManagedIdentityMsalClient(request))
+ .thenReturn(TestUtils.getMockAccessToken(token, expiresAt));
+ })) {
+ applicationMock.when(ManagedIdentityApplication::getManagedIdentitySource)
+ .thenReturn(ManagedIdentitySourceType.AZURE_ARC);
+
+ // test
+ ManagedIdentityCredentialBuilder builder
+ = new ManagedIdentityCredentialBuilder().configuration(configuration);
+ if (clientId != null) {
+ builder.clientId(clientId);
+ } else if (resourceId != null) {
+ builder.resourceId(resourceId);
+ } else {
+ builder.objectId(objectId);
+ }
+
+ StepVerifier.create(builder.build().getToken(request))
+ .expectNextMatches(accessToken -> token.equals(accessToken.getToken())
+ && expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
+ .verifyComplete();
+ assertEquals(1, identityClientMock.constructed().size());
+ }
}
@Test
public void testCloudshellUserAssigned() {
// setup
- String endpoint = "http://localhost";
TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com");
- Configuration configuration
- = TestUtils.createTestConfiguration(new TestConfigurationSource().put("MSI_ENDPOINT", endpoint));
+ Configuration configuration = TestUtils.createTestConfiguration(new TestConfigurationSource());
- // test
- ManagedIdentityCredential credential
- = new ManagedIdentityCredentialBuilder().configuration(configuration).objectId(OBJECT_ID).build();
- StepVerifier.create(credential.getToken(request))
- .expectErrorMatches(t -> t instanceof ClientAuthenticationException)
- .verify();
+ try (MockedStatic applicationMock = mockStatic(ManagedIdentityApplication.class)) {
+ applicationMock.when(ManagedIdentityApplication::getManagedIdentitySource)
+ .thenReturn(ManagedIdentitySourceType.CLOUD_SHELL);
+
+ // test
+ ManagedIdentityCredential credential
+ = new ManagedIdentityCredentialBuilder().configuration(configuration).objectId(OBJECT_ID).build();
+ StepVerifier.create(credential.getToken(request))
+ .expectErrorMatches(t -> t instanceof CredentialUnavailableException
+ && t.getMessage().contains("User-assigned managed identity is not supported in CLOUD_SHELL"))
+ .verify();
+ }
+ }
+
+ @Test
+ public void testServiceFabricUserAssigned() {
+ // setup
+ TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com");
+ Configuration configuration = TestUtils.createTestConfiguration(new TestConfigurationSource());
+
+ try (MockedStatic applicationMock = mockStatic(ManagedIdentityApplication.class)) {
+ applicationMock.when(ManagedIdentityApplication::getManagedIdentitySource)
+ .thenReturn(ManagedIdentitySourceType.SERVICE_FABRIC);
+
+ // test
+ ManagedIdentityCredential credential
+ = new ManagedIdentityCredentialBuilder().configuration(configuration).resourceId(RESOURCE_ID).build();
+ StepVerifier.create(credential.getToken(request))
+ .expectErrorMatches(t -> t instanceof CredentialUnavailableException
+ && t.getMessage().contains("Service Fabric managed identity environment"))
+ .verify();
+ }
}
@Test
public void testInvalidIdCombination() {
// setup
- String resourceId = "/subscriptions/" + UUID.randomUUID()
- + "/resourcegroups/aresourcegroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ident";
String objectId = "2323-sd2323s-32323-32334-34343";
// test
assertThrows(IllegalStateException.class,
- () -> new ManagedIdentityCredentialBuilder().clientId(CLIENT_ID).resourceId(resourceId).build());
+ () -> new ManagedIdentityCredentialBuilder().clientId(CLIENT_ID).resourceId(RESOURCE_ID).build());
assertThrows(IllegalStateException.class,
() -> new ManagedIdentityCredentialBuilder().clientId(CLIENT_ID)
- .resourceId(resourceId)
+ .resourceId(RESOURCE_ID)
.objectId(objectId)
.build());
@@ -181,7 +238,7 @@ public void testInvalidIdCombination() {
() -> new ManagedIdentityCredentialBuilder().clientId(CLIENT_ID).objectId(objectId).build());
assertThrows(IllegalStateException.class,
- () -> new ManagedIdentityCredentialBuilder().resourceId(resourceId).objectId(objectId).build());
+ () -> new ManagedIdentityCredentialBuilder().resourceId(RESOURCE_ID).objectId(objectId).build());
}
@Test