Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/versioning/external_dependencies.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ com.microsoft.azure:azure-mgmt-graph-rbac;1.3.0
com.microsoft.azure:azure-mgmt-network;1.33.0
com.microsoft.azure:azure-mgmt-resources;1.3.0
com.microsoft.azure:azure-storage;8.0.0
com.microsoft.azure:msal4j;1.23.1
com.microsoft.azure:msal4j;1.26.0
com.microsoft.azure:msal4j-brokers;1.0.0
com.microsoft.azure:msal4j-persistence-extension;1.3.0
io.opentelemetry:opentelemetry-api;1.58.0
Expand Down
2 changes: 1 addition & 1 deletion sdk/eventhubs/microsoft-azure-eventhubs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.23.1</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
<version>1.26.0</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
<scope>test</scope>
</dependency>
<dependency>
Expand Down
8 changes: 8 additions & 0 deletions sdk/identity-v2/azure-identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@

### Features Added

- Added support for user-assigned managed identities on Azure Arc in `ManagedIdentityCredential` and `DefaultAzureCredential`.

### Breaking Changes

### Bugs Fixed

- Fixed `DefaultAzureCredential` failing after a credential successfully acquired a token.

### Other Changes

#### Dependency Updates

- Upgraded `msal4j` from `1.23.1` to `1.26.0`.
4 changes: 2 additions & 2 deletions sdk/identity-v2/azure-identity/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.23.1</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
<version>1.26.0</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
</dependency>

<dependency>
Expand Down Expand Up @@ -125,7 +125,7 @@
<rules>
<bannedDependencies>
<includes>
<include>com.microsoft.azure:msal4j:[1.23.1]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.microsoft.azure:msal4j:[1.26.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.microsoft.azure:msal4j-persistence-extension:[1.3.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j-persistence-extension;external_dependency} -->
</includes>
</bannedDependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
* Sets</a></li>
* </ol>
*
* <p>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.</p>
*
* <p><strong>Sample: Construct a simple ManagedIdentityCredential</strong></p>
*
* <p>The following code sample demonstrates the creation of a ManagedIdentityCredential,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
* <a href="https://aka.ms/azsdk/java/identity/managedidentitycredential/docs">managed identity authentication
* documentation</a>.</p>
*
* <p>Azure Arc-enabled servers support user-assigned managed identities selected by client ID, resource ID, or object
* ID.</p>
*
* <p><strong>Sample: Construct a simple ManagedIdentityCredential</strong></p>
*
* <p>The following code sample demonstrates the creation of a {@link ManagedIdentityCredential},
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ManagedIdentityApplication> applicationMock = mockStatic(ManagedIdentityApplication.class);
MockedConstruction<ManagedIdentityClient> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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<ManagedIdentityApplication> applicationMock = mockStatic(ManagedIdentityApplication.class);
MockedConstruction<ManagedIdentityClient> 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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR says the Service Fabric restriction is preserved, but v2 has no matching regression test. Please add a v2 Service Fabric test or clarify that MSAL handles this restriction.


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<ManagedIdentityApplication> 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"));
}
}
}
4 changes: 2 additions & 2 deletions sdk/identity/azure-identity-broker/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.23.1</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
<version>1.26.0</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a changelog entry for this msal4j upgrade. azure-identity-broker ships this runtime dependency, so its unreleased changelog should mention the update from 1.23.1 to 1.26.0.

</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
Expand All @@ -67,7 +67,7 @@
<rules>
<bannedDependencies>
<includes>
<include>com.microsoft.azure:msal4j:[1.23.1]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.microsoft.azure:msal4j:[1.26.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.microsoft.azure:msal4j-brokers:[1.0.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j-brokers;external_dependency} -->
</includes>
</bannedDependencies>
Expand Down
6 changes: 6 additions & 0 deletions sdk/identity/azure-identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features Added

- Added support for user-assigned managed identities on Azure Arc in `ManagedIdentityCredential` and `DefaultAzureCredential`.

### Breaking Changes

### Bugs Fixed
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions sdk/identity/azure-identity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions sdk/identity/azure-identity/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.23.1</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
<version>1.26.0</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
Expand Down Expand Up @@ -109,7 +109,7 @@
<rules>
<bannedDependencies>
<includes>
<include>com.microsoft.azure:msal4j:[1.23.1]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.microsoft.azure:msal4j:[1.26.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.microsoft.azure:msal4j-persistence-extension:[1.3.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j-persistence-extension;external_dependency} -->
<include>net.java.dev.jna:jna-platform:[5.17.0]</include> <!-- {x-include-update;net.java.dev.jna:jna-platform;external_dependency} -->
</includes>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -43,13 +42,6 @@ class ArcIdentityCredential extends ManagedIdentityServiceCredential {
* @return A publisher that emits an {@link AccessToken}.
*/
public Mono<AccessToken> 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);
}
}
Loading