Skip to content

Commit 97b8977

Browse files
authored
add OS default broker auth to DAC (#45891)
1 parent 7128c46 commit 97b8977

File tree

6 files changed

+144
-10
lines changed

6 files changed

+144
-10
lines changed

sdk/identity/azure-identity/TROUBLESHOOTING.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ This troubleshooting guide covers failure investigation techniques, common error
2727
- [Troubleshoot Web Account Manager (WAM) brokered authentication issues](#troubleshoot-web-account-manager-wam-brokered-authentication-issues)
2828
- [Get additional help](#get-additional-help)
2929

30+
3031
## Handle Azure Identity exceptions
3132

3233
### ClientAuthenticationException
@@ -350,9 +351,16 @@ You can find more info about Fork Join Pool [here](https://docs.oracle.com/javas
350351

351352
## Troubleshoot Web Account Manager (WAM) brokered authentication issues
352353

354+
Broker authentication is used by `DefaultAzureCredential` to enable secure sign-in via the Windows Web Account Manager (WAM). This mechanism requires the `azure-identity-broker` dependency and is currently only supported on Windows.
355+
353356
| Error Message |Description| Mitigation |
354357
|---|---|---|
355358
|AADSTS50011|The application is missing the expected redirect URI.|Ensure that one of redirect URIs registered for the Microsoft Entra application matches the following URI pattern: `ms-appx-web://Microsoft.AAD.BrokerPlugin/{client_id}`|
359+
| `CredentialUnavailableException: azure-identity-broker dependency is not available. Ensure you have azure-identity-broker dependency added to your application.` | The required broker dependency is missing from your project. | Add the `azure-identity-broker` dependency to your application's build configuration (e.g., Maven or Gradle). |
360+
| `CredentialUnavailableException: InteractiveBrowserBrokerCredentialBuilder class not found. Ensure you have azure-identity-broker dependency added to your application.` | The broker credential builder class could not be found, likely due to a missing or misconfigured dependency. | Ensure the `azure-identity-broker` dependency is present and correctly configured in your project. |
361+
| `CredentialUnavailableException: Failed to create InteractiveBrowserBrokerCredential dynamically` | An unexpected error occurred while creating the broker credential. | Check the inner exception for more details. Ensure your environment meets all requirements for broker authentication (Windows OS, correct dependencies, and configuration). |
362+
363+
> **Note:** Broker authentication is currently only supported on Windows. macOS and Linux are not yet supported.
356364
357365
### Unable to log in with Microsoft account (MSA) on Windows
358366

@@ -372,6 +380,7 @@ You may also log in another MSA account by selecting "Microsoft account":
372380

373381
![Microsoft account](./images/MSA4.png)
374382

383+
---
375384

376385
## Get additional help
377386

sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredentialBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ private void addDevCredentials(List<TokenCredential> credentials) {
338338
credentials.add(new AzureCliCredential(tenantId, identityClientOptions.clone()));
339339
credentials.add(new AzurePowerShellCredential(tenantId, identityClientOptions.clone()));
340340
credentials.add(new AzureDeveloperCliCredential(tenantId, identityClientOptions.clone()));
341+
credentials.add(new OSBrokerCredential(tenantId));
341342
}
342343

343344
private WorkloadIdentityCredential getWorkloadIdentityCredential() {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.identity;
5+
6+
import com.azure.core.credential.TokenCredential;
7+
import com.azure.core.credential.AccessToken;
8+
import com.azure.core.credential.TokenRequestContext;
9+
import com.azure.core.util.CoreUtils;
10+
import com.azure.core.util.logging.ClientLogger;
11+
import com.azure.identity.implementation.util.IdentityUtil;
12+
13+
import reactor.core.publisher.Mono;
14+
import java.util.concurrent.atomic.AtomicReference;
15+
16+
class OSBrokerCredential implements TokenCredential {
17+
private static final ClientLogger LOGGER = new ClientLogger(OSBrokerCredential.class);
18+
private static final String BROKER_BUILDER_CLASS
19+
= "com.azure.identity.broker.InteractiveBrowserBrokerCredentialBuilder";
20+
private final String tenantId;
21+
private final AtomicReference<TokenCredential> cached = new AtomicReference<>();
22+
23+
OSBrokerCredential(String tenantId) {
24+
this.tenantId = tenantId;
25+
}
26+
27+
@Override
28+
public Mono<AccessToken> getToken(TokenRequestContext request) {
29+
TokenCredential credential = cached.get();
30+
if (credential == null) {
31+
TokenCredential newCredential;
32+
try {
33+
// Create the broker credential dynamically
34+
newCredential = createBrokerCredential();
35+
} catch (CredentialUnavailableException e) {
36+
// If the broker is unavailable, throw the exception
37+
return Mono.error(e);
38+
} catch (Exception e) {
39+
// Log and throw any other exceptions that occur during credential creation
40+
return Mono.error(LOGGER.logExceptionAsError(
41+
new CredentialUnavailableException("Failed to create OS Broker credential.", e)));
42+
}
43+
if (cached.compareAndSet(null, newCredential)) {
44+
credential = newCredential;
45+
} else {
46+
credential = cached.get();
47+
}
48+
}
49+
return credential.getToken(request);
50+
}
51+
52+
private TokenCredential createBrokerCredential() {
53+
final String troubleshoot
54+
= " To mitigate this issue, refer to http://aka.ms/azsdk/java/identity/dacbrokerauth/troubleshoot";
55+
if (!IdentityUtil.isBrokerAvailable()) {
56+
throw LOGGER.logExceptionAsError(
57+
new CredentialUnavailableException("azure-identity-broker dependency is not available. "
58+
+ "Ensure you have azure-identity-broker dependency added to your application." + troubleshoot));
59+
}
60+
try {
61+
Class<?> builderClass = Class.forName(BROKER_BUILDER_CLASS);
62+
Object builder = builderClass.getConstructor().newInstance();
63+
builderClass.getMethod("setWindowHandle", long.class).invoke(builder, 0);
64+
builderClass.getMethod("useDefaultBrokerAccount").invoke(builder);
65+
com.azure.identity.InteractiveBrowserCredentialBuilder browserCredentialBuilder
66+
= (com.azure.identity.InteractiveBrowserCredentialBuilder) builder;
67+
if (!CoreUtils.isNullOrEmpty(tenantId)) {
68+
builderClass.getMethod("tenantId", String.class).invoke(builder, tenantId);
69+
}
70+
return browserCredentialBuilder.build();
71+
} catch (ClassNotFoundException e) {
72+
throw LOGGER
73+
.logExceptionAsError(new CredentialUnavailableException(
74+
"InteractiveBrowserBrokerCredentialBuilder class not found. "
75+
+ "Ensure you have azure-identity-broker dependency added to your application." + troubleshoot,
76+
e));
77+
} catch (Exception e) {
78+
throw LOGGER.logExceptionAsError(new CredentialUnavailableException(
79+
"Failed to create InteractiveBrowserBrokerCredential dynamically." + troubleshoot, e));
80+
}
81+
}
82+
}

sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/util/IdentityUtil.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,14 +179,16 @@ public static boolean isLinuxPlatform() {
179179
}
180180

181181
public static boolean isVsCodeBrokerAuthAvailable() {
182+
// Check if VS Code broker auth record file exists
183+
File authRecordFile = VSCODE_AUTH_RECORD_PATH.toFile();
184+
return isBrokerAvailable() && authRecordFile.exists() && authRecordFile.isFile();
185+
}
186+
187+
public static boolean isBrokerAvailable() {
182188
try {
183189
// 1. Check if Broker dependency is available
184190
Class.forName("com.azure.identity.broker.InteractiveBrowserBrokerCredentialBuilder");
185-
186-
// 2. Check if VS Code broker auth record file exists
187-
File authRecordFile = VSCODE_AUTH_RECORD_PATH.toFile();
188-
189-
return authRecordFile.exists() && authRecordFile.isFile();
191+
return true;
190192
} catch (ClassNotFoundException e) {
191193
return false; // Broker not present
192194
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.identity;
5+
6+
import com.azure.core.credential.TokenRequestContext;
7+
import org.junit.jupiter.api.Test;
8+
import reactor.test.StepVerifier;
9+
10+
public class DacOsBrokerCredentialTest {
11+
@Test
12+
public void testBrokerUnavailable() {
13+
// setup
14+
TokenRequestContext request
15+
= new TokenRequestContext().addScopes("https://vault.azure.net/.default").setTenantId("newTenant");
16+
17+
// Simulate broker unavailable by using a test double or by ensuring the environment does not support broker
18+
OSBrokerCredential credential = new OSBrokerCredential("tenant");
19+
StepVerifier.create(credential.getToken(request))
20+
.expectErrorMatches(e -> e instanceof CredentialUnavailableException
21+
&& e.getMessage().contains("azure-identity-broker dependency is not available")
22+
&& e.getMessage()
23+
.contains(
24+
"To mitigate this issue, refer to http://aka.ms/azsdk/java/identity/dacbrokerauth/troubleshoot"))
25+
.verify();
26+
}
27+
}

sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,12 @@ public void testNoCredentialWorks() {
321321
= mockConstruction(IntelliJCredential.class, (intelliJCredential, context) -> {
322322
when(intelliJCredential.getToken(request)).thenReturn(
323323
Mono.error(new CredentialUnavailableException("Cannot get token from IntelliJ Credential")));
324+
});
325+
MockedConstruction<OSBrokerCredential> osBrokerCredentialMock
326+
= mockConstruction(OSBrokerCredential.class, (osBrokerCredential, context) -> {
327+
when(osBrokerCredential.getToken(request)).thenReturn(
328+
Mono.error(new CredentialUnavailableException("Cannot get token from OS Broker credential")));
324329
})) {
325-
326330
// test
327331
DefaultAzureCredential credential
328332
= new DefaultAzureCredentialBuilder().configuration(configuration).build();
@@ -335,6 +339,7 @@ public void testNoCredentialWorks() {
335339
Assertions.assertNotNull(azureDeveloperCliCredentialMock);
336340
Assertions.assertNotNull(azurePowerShellCredentialMock);
337341
Assertions.assertNotNull(intelliJCredentialMock);
342+
Assertions.assertNotNull(osBrokerCredentialMock);
338343
}
339344
}
340345

@@ -368,7 +373,13 @@ public void testCredentialUnavailable() {
368373
= mockConstruction(AzureDeveloperCliCredential.class, (AzureDeveloperCliCredential, context) -> {
369374
when(AzureDeveloperCliCredential.getToken(request)).thenReturn(Mono.error(
370375
new CredentialUnavailableException("Cannot get token from Azure Developer CLI credential")));
376+
});
377+
MockedConstruction<OSBrokerCredential> osBrokerCredentialMock
378+
= mockConstruction(OSBrokerCredential.class, (osBrokerCredential, context) -> {
379+
when(osBrokerCredential.getToken(request)).thenReturn(
380+
Mono.error(new CredentialUnavailableException("Cannot get token from OS Broker credential")));
371381
})) {
382+
372383
// test
373384
DefaultAzureCredential credential
374385
= new DefaultAzureCredentialBuilder().configuration(configuration).build();
@@ -381,8 +392,8 @@ public void testCredentialUnavailable() {
381392
Assertions.assertNotNull(powerShellCredentialMock);
382393
Assertions.assertNotNull(azureCliCredentialMock);
383394
Assertions.assertNotNull(azureDeveloperCliCredentialMock);
395+
Assertions.assertNotNull(osBrokerCredentialMock);
384396
}
385-
386397
}
387398

388399
@Test
@@ -659,14 +670,15 @@ public void testDeveloperOnlyCredentialsChain(String devValue) {
659670
List<TokenCredential> credentials = extractCredentials(credential);
660671

661672
// Only developer credentials should be present (4)
662-
assertEquals(5, credentials.size());
673+
assertEquals(6, credentials.size());
663674

664675
// Verify developer credentials in order
665676
assertInstanceOf(IntelliJCredential.class, credentials.get(0));
666677
assertInstanceOf(VisualStudioCodeCredential.class, credentials.get(1));
667678
assertInstanceOf(AzureCliCredential.class, credentials.get(2));
668679
assertInstanceOf(AzurePowerShellCredential.class, credentials.get(3));
669680
assertInstanceOf(AzureDeveloperCliCredential.class, credentials.get(4));
681+
assertInstanceOf(OSBrokerCredential.class, credentials.get(5));
670682
}
671683

672684
@ParameterizedTest
@@ -772,8 +784,8 @@ public void testDefaultCredentialChainWithoutFilter() {
772784
// Extract credentials to check their types and order
773785
List<TokenCredential> credentials = extractCredentials(credential);
774786

775-
// Verify the complete chain with all 7 credentials
776-
assertEquals(8, credentials.size());
787+
// Verify the complete chain with all 9 credentials
788+
assertEquals(9, credentials.size());
777789
assertInstanceOf(EnvironmentCredential.class, credentials.get(0));
778790
assertInstanceOf(WorkloadIdentityCredential.class, credentials.get(1));
779791
assertInstanceOf(ManagedIdentityCredential.class, credentials.get(2));
@@ -782,6 +794,7 @@ public void testDefaultCredentialChainWithoutFilter() {
782794
assertInstanceOf(AzureCliCredential.class, credentials.get(5));
783795
assertInstanceOf(AzurePowerShellCredential.class, credentials.get(6));
784796
assertInstanceOf(AzureDeveloperCliCredential.class, credentials.get(7));
797+
assertInstanceOf(OSBrokerCredential.class, credentials.get(8));
785798
}
786799

787800
/**

0 commit comments

Comments
 (0)