Skip to content

Commit 54424da

Browse files
authored
Integrate Broker Auth in VS Code Auth Flow. (Azure#45715)
1 parent c67ef6a commit 54424da

File tree

9 files changed

+168
-80
lines changed

9 files changed

+168
-80
lines changed

sdk/identity/azure-identity/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
## 1.17.0-beta.1 (Unreleased)
44

5+
56
### Features Added
67

8+
- **Visual Studio Code Credential** has been re-enabled and now supports **broker authentication** using the Azure account signed in via Visual Studio Code. [#45715](https://github.com/Azure/azure-sdk-for-java/pull/45715)
9+
710
### Breaking Changes
811

912
#### Behavioral Breaking Changes

sdk/identity/azure-identity/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,37 @@ public void createDefaultAzureCredentialForIntelliJ() {
162162
}
163163
```
164164

165+
### Authenticate Using Visual Studio Code with `DefaultAzureCredential`
166+
167+
To authenticate using Visual Studio Code, ensure you have signed in through the **Azure Resources** extension. The signed-in user is then picked up automatically by `DefaultAzureCredential` in the Azure SDK for Java.
168+
169+
#### Prerequisites
170+
171+
- [Azure Resources Extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureresourcegroups) is installed in Visual Studio Code.
172+
- You are signed in using the `Azure: Sign In` command in VS Code.
173+
- Your project includes the [`azure-identity-borker`](https://search.maven.org/artifact/com.azure/azure-identity-broker) package.
174+
175+
#### Example: Use `DefaultAzureCredential` with Key Vault
176+
177+
The following example demonstrates authenticating the `SecretClient` from the [`azure-security-keyvault-secrets`](https://learn.microsoft.com/java/api/overview/azure/security-keyvault-secrets-readme?view=azure-java-stable) client library using `DefaultAzureCredential`:
178+
179+
```java
180+
/**
181+
* DefaultAzureCredential uses the signed-in user from Visual Studio Code
182+
* via the Azure Resources extension.
183+
*/
184+
public void createDefaultAzureCredentialForVSCode() {
185+
DefaultAzureCredential defaultCredential = new DefaultAzureCredentialBuilder()
186+
.build();
187+
188+
// Azure SDK client builders accept the credential as a parameter
189+
SecretClient client = new SecretClientBuilder()
190+
.vaultUrl("https://{YOUR_VAULT_NAME}.vault.azure.net")
191+
.credential(defaultCredential)
192+
.buildClient();
193+
}
194+
```
195+
165196
## Managed Identity support
166197

167198
The [Managed identity authentication](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview) is supported indirectly via `DefaultAzureCredential` or directly via `ManagedIdentityCredential` for the following Azure Services:

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,9 @@ private ArrayList<TokenCredential> getCredentialsChain() {
304304
output.add(new AzureCliCredential(tenantId, identityClientOptions.clone()));
305305
output.add(new AzurePowerShellCredential(tenantId, identityClientOptions.clone()));
306306
output.add(new AzureDeveloperCliCredential(tenantId, identityClientOptions.clone()));
307+
if (IdentityUtil.isVsCodeBrokerAuthAvailable()) {
308+
output.add(new VisualStudioCodeCredential(tenantId, identityClientOptions.clone()));
309+
}
307310
}
308311

309312
return output;

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

Lines changed: 58 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,40 @@
66
import com.azure.core.credential.AccessToken;
77
import com.azure.core.credential.TokenCredential;
88
import com.azure.core.credential.TokenRequestContext;
9-
import com.azure.core.exception.ClientAuthenticationException;
109
import com.azure.core.util.CoreUtils;
1110
import com.azure.core.util.logging.ClientLogger;
12-
import com.azure.identity.implementation.IdentityClient;
13-
import com.azure.identity.implementation.IdentityClientBuilder;
1411
import com.azure.identity.implementation.IdentityClientOptions;
15-
import com.azure.identity.implementation.MsalToken;
16-
import com.azure.identity.implementation.VisualStudioCacheAccessor;
1712
import com.azure.identity.implementation.util.LoggingUtil;
1813
import reactor.core.publisher.Mono;
1914

20-
import java.util.Map;
21-
import java.util.concurrent.atomic.AtomicReference;
15+
import static com.azure.identity.implementation.util.IdentityUtil.isVsCodeBrokerAuthAvailable;
16+
import static com.azure.identity.implementation.util.IdentityUtil.loadVSCodeAuthRecord;
2217

2318
/**
24-
* <p>Enables authentication to Microsoft Entra ID as the user signed in to Visual Studio Code via
25-
* the 'Azure Account' extension.</p>
19+
* Enables authentication to Microsoft Entra ID using the user account signed in through the
20+
* <a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureresourcegroups">
21+
* Azure Resources</a> extension in Visual Studio Code.
2622
*
27-
* <p>It's a <a href="https://github.com/Azure/azure-sdk-for-java/issues/27364">known issue</a> that this credential
28-
* doesn't work with <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.azure-account">Azure
29-
* Account extension</a> versions newer than <strong>0.9.11</strong>. A long-term fix to this problem is in progress.
30-
* In the meantime, consider authenticating with {@link AzureCliCredential}.</p>
23+
* <p><b>Prerequisites:</b></p>
24+
* <ol>
25+
* <li>Install the
26+
* <a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureresourcegroups">
27+
* Azure Resources</a> extension in Visual Studio Code and sign in using the <b>Azure: Sign In</b> command.</li>
28+
* <li>Add the
29+
* <a href="https://central.sonatype.com/artifact/com.azure/azure-identity-broker">
30+
* azure-identity-broker</a> dependency to your project's build configuration.</li>
31+
* </ol>
3132
*
3233
* @see com.azure.identity
3334
* @see VisualStudioCodeCredentialBuilder
34-
*
35-
* @deprecated This credential is deprecated because the VS Code Azure Account extension on which this credential
36-
* relies has been deprecated. Users should use other dev-time credentials, such as {@link AzureCliCredential},
37-
* {@link AzureDeveloperCliCredential}, {@link AzurePowerShellCredential} or {@link IntelliJCredential} for their
38-
* local development needs. See <a href="https://github.com/microsoft/vscode-azure-account/issues/964">this issue</a>
39-
* for Azure Account extension deprecation notice.
4035
*/
41-
@Deprecated
4236
public class VisualStudioCodeCredential implements TokenCredential {
43-
private final IdentityClient identityClient;
44-
private final AtomicReference<MsalToken> cachedToken;
45-
private final String cloudInstance;
4637
private static final ClientLogger LOGGER = new ClientLogger(VisualStudioCodeCredential.class);
47-
48-
private static final String TROUBLESHOOTING
49-
= "VisualStudioCodeCredential is affected by known issues. See https://aka.ms/azsdk/java/identity/troubleshoot#troubleshoot-visualstudiocodecredential-authentication-issues for more information.";
38+
private static final String VSCODE_CLIENT_ID = "aebc6443-996d-45c2-90f0-388ff96faa56";
39+
private static final String BROKER_BUILDER_CLASS
40+
= "com.azure.identity.broker.InteractiveBrowserBrokerCredentialBuilder";
41+
private final IdentityClientOptions clientOptions;
42+
private final String tenant;
5043

5144
/**
5245
* Creates a public class VisualStudioCodeCredential implements TokenCredential with the given tenant and
@@ -56,60 +49,57 @@ public class VisualStudioCodeCredential implements TokenCredential {
5649
* @param identityClientOptions the options for configuring the identity client
5750
*/
5851
VisualStudioCodeCredential(String tenantId, IdentityClientOptions identityClientOptions) {
59-
60-
IdentityClientOptions options
61-
= (identityClientOptions == null ? new IdentityClientOptions() : identityClientOptions);
62-
String tenant;
63-
64-
VisualStudioCacheAccessor accessor = new VisualStudioCacheAccessor();
65-
Map<String, String> userSettings = accessor.getUserSettingsDetails();
66-
67-
cloudInstance = userSettings.get("cloud");
68-
if (CoreUtils.isNullOrEmpty(options.getAuthorityHost())) {
69-
options.setAuthorityHost(accessor.getAzureAuthHost(cloudInstance));
70-
}
52+
clientOptions = (identityClientOptions == null ? new IdentityClientOptions() : identityClientOptions);
7153

7254
if (!CoreUtils.isNullOrEmpty(tenantId)) {
7355
tenant = tenantId;
7456
} else {
75-
tenant = userSettings.getOrDefault("tenant", "common");
57+
tenant = null;
7658
}
77-
78-
identityClient = new IdentityClientBuilder().tenantId(tenant)
79-
.clientId("aebc6443-996d-45c2-90f0-388ff96faa56")
80-
.identityClientOptions(options)
81-
.build();
82-
83-
this.cachedToken = new AtomicReference<>();
8459
}
8560

8661
@Override
8762
public Mono<AccessToken> getToken(TokenRequestContext request) {
8863
return Mono.defer(() -> {
89-
if (cachedToken.get() != null) {
90-
return identityClient.authenticateWithPublicClientCache(request, cachedToken.get().getAccount())
91-
.onErrorResume(t -> Mono.empty());
64+
TokenCredential brokerAuthCredential = getBrokerAuthCredential(VSCODE_CLIENT_ID);
65+
if (brokerAuthCredential != null) {
66+
return brokerAuthCredential.getToken(request);
9267
} else {
93-
return Mono.empty();
68+
return Mono
69+
.error(new CredentialUnavailableException("Visual Studio Code Authentication is not available."
70+
+ " Ensure you have azure-identity-broker dependency added to your application."
71+
+ " Then ensure, you have signed into Azure via VS Code and have Azure Resources Extension installed in VS Code."));
9472
}
95-
})
96-
.switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithVsCodeCredential(request, cloudInstance)))
97-
.map(msalToken -> {
98-
cachedToken.set(msalToken);
99-
return (AccessToken) msalToken;
100-
})
101-
.doOnNext(token -> LoggingUtil.logTokenSuccess(LOGGER, request))
102-
.doOnError(error -> {
103-
Throwable other = null;
104-
if (error instanceof CredentialUnavailableException) {
105-
other = new CredentialUnavailableException(TROUBLESHOOTING, error);
73+
}).doOnNext(token -> LoggingUtil.logTokenSuccess(LOGGER, request)).doOnError(error -> {
74+
LoggingUtil.logTokenError(LOGGER, clientOptions, request, error);
75+
});
76+
}
10677

107-
} else if (error instanceof ClientAuthenticationException) {
108-
other = new ClientAuthenticationException(TROUBLESHOOTING, null, error);
109-
} else {
110-
other = error;
111-
}
112-
LoggingUtil.logTokenError(LOGGER, identityClient.getIdentityClientOptions(), request, other);
113-
});
78+
TokenCredential getBrokerAuthCredential(String clientId) {
79+
if (!isVsCodeBrokerAuthAvailable()) {
80+
return null;
81+
}
82+
try {
83+
Class<?> builderClass = Class.forName(BROKER_BUILDER_CLASS);
84+
Object builder = builderClass.getConstructor().newInstance();
85+
AuthenticationRecord authenticationRecord = loadVSCodeAuthRecord();
86+
builderClass.getMethod("setWindowHandle", long.class).invoke(builder, 0);
87+
InteractiveBrowserCredentialBuilder browserCredentialBuilder
88+
= (InteractiveBrowserCredentialBuilder) builder;
89+
browserCredentialBuilder.clientId(clientId);
90+
browserCredentialBuilder.authenticationRecord(authenticationRecord);
91+
if (CoreUtils.isNullOrEmpty(tenant)) {
92+
builderClass.getMethod("tenantId", String.class).invoke(builder, authenticationRecord.getTenantId());
93+
} else {
94+
builderClass.getMethod("tenantId", String.class).invoke(builder, tenant);
95+
}
96+
return browserCredentialBuilder.build();
97+
} catch (ClassNotFoundException e) {
98+
// Broker not on classpath
99+
return null;
100+
} catch (Exception e) {
101+
throw LOGGER.logExceptionAsError(
102+
new CredentialUnavailableException("Failed to create VisualStudioCodeCredential dynamically", e));
103+
}
114104
}
115105
}

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,23 @@
1313
/**
1414
* Fluent credential builder for instantiating a {@link VisualStudioCodeCredential}.
1515
*
16-
* <p>It's a <a href="https://github.com/Azure/azure-sdk-for-java/issues/27364">known issue</a> that this credential
17-
* doesn't work with <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.azure-account">Azure
18-
* Account extension</a> versions newer than <strong>0.9.11</strong>. A long-term fix to this problem is in progress.
19-
* In the meantime, consider authenticating with {@link AzureCliCredential}.</p>
16+
* Enables authentication to Microsoft Entra ID using the user account signed in through the
17+
* <a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureresourcegroups">
18+
* Azure Resources</a> extension in Visual Studio Code.
19+
*
20+
* <p><b>Prerequisites:</b></p>
21+
* <ol>
22+
* <li>Install the
23+
* <a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureresourcegroups">
24+
* Azure Resources</a> extension in Visual Studio Code and sign in using the <b>Azure: Sign In</b> command.</li>
25+
* <li>Add the
26+
* <a href="https://central.sonatype.com/artifact/com.azure/azure-identity-broker">
27+
* azure-identity-broker</a> dependency to your project's build configuration.</li>
28+
* </ol>
2029
*
2130
* @see VisualStudioCodeCredential
2231
*
23-
* @deprecated This credential is deprecated because the VS Code Azure Account extension on which this credential
24-
* relies has been deprecated. Users should use other dev-time credentials, such as {@link AzureCliCredential},
25-
* {@link AzureDeveloperCliCredential}, {@link AzurePowerShellCredential} or {@link IntelliJCredential} for their
26-
* local development needs. See <a href="https://github.com/microsoft/vscode-azure-account/issues/964">this issue</a>
27-
* for Azure Account extension deprecation notice.
2832
*/
29-
@Deprecated
3033
public class VisualStudioCodeCredentialBuilder extends CredentialBuilderBase<VisualStudioCodeCredentialBuilder> {
3134
private static final ClientLogger LOGGER = new ClientLogger(VisualStudioCodeCredentialBuilder.class);
3235

sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,8 @@ public Mono<MsalToken> authenticateWithBrowserInteraction(TokenRequestContext re
819819
// a null account to MSAL. If that fails, show the dialog.
820820

821821
return getPublicClientInstance(request).getValue().flatMap(pc -> {
822-
if (options.isBrokerEnabled() && options.useDefaultBrokerAccount()) {
822+
if (options.isBrokerEnabled()
823+
&& (options.useDefaultBrokerAccount() || options.getAuthenticationRecord() != null)) {
823824
return Mono.fromFuture(() -> acquireTokenFromPublicClientSilently(request, pc, null, false))
824825
// The error case here represents the silent acquisition failing. There's nothing actionable and
825826
// in this case the fallback path of showing the dialog will capture any meaningful error and share it.

sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentitySyncClient.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,8 @@ public MsalToken authenticateWithBrowserInteraction(TokenRequestContext request,
323323
// If the broker is enabled, try to get the token for the default account by passing
324324
// a null account to MSAL. If that fails, show the dialog.
325325
MsalToken token = null;
326-
if (options.isBrokerEnabled() && options.useDefaultBrokerAccount()) {
326+
if (options.isBrokerEnabled()
327+
&& (options.useDefaultBrokerAccount() || options.getAuthenticationRecord() != null)) {
327328
try {
328329
token = acquireTokenFromPublicClientSilently(request, pc, null, false);
329330
} catch (Exception e) {

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.azure.core.util.Configuration;
1010
import com.azure.core.util.CoreUtils;
1111
import com.azure.core.util.logging.ClientLogger;
12+
import com.azure.identity.AuthenticationRecord;
1213
import com.azure.identity.BrowserCustomizationOptions;
1314
import com.azure.identity.implementation.IdentityClientOptions;
1415
import com.azure.json.JsonProviders;
@@ -20,12 +21,17 @@
2021
import java.io.InputStream;
2122
import java.io.IOException;
2223
import java.io.UncheckedIOException;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.nio.file.Paths;
2327
import java.util.Arrays;
2428
import java.util.Collections;
2529
import java.util.List;
2630
import java.util.Map;
2731

2832
public final class IdentityUtil {
33+
public static final Path VSCODE_AUTH_RECORD_PATH = Paths.get(System.getProperty("user.home"), ".azure",
34+
"ms-azuretools.vscode-azureresourcegroups", "authRecord.json");
2935
private static final ClientLogger LOGGER = new ClientLogger(IdentityUtil.class);
3036
public static final String AZURE_ADDITIONALLY_ALLOWED_TENANTS = "AZURE_ADDITIONALLY_ALLOWED_TENANTS";
3137
public static final String ALL_TENANTS = "*";
@@ -171,4 +177,30 @@ public static boolean isWindowsPlatform() {
171177
public static boolean isLinuxPlatform() {
172178
return System.getProperty("os.name").contains("Linux");
173179
}
180+
181+
public static boolean isVsCodeBrokerAuthAvailable() {
182+
try {
183+
// 1. Check if Broker dependency is available
184+
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();
190+
} catch (ClassNotFoundException e) {
191+
return false; // Broker not present
192+
}
193+
}
194+
195+
public static AuthenticationRecord loadVSCodeAuthRecord() throws IOException {
196+
// Resolve the full path to authRecord.json
197+
File file = VSCODE_AUTH_RECORD_PATH.toFile();
198+
if (!file.exists()) {
199+
return null;
200+
}
201+
// Read file content
202+
InputStream json = Files.newInputStream(VSCODE_AUTH_RECORD_PATH);
203+
// Deserialize to AuthenticationRecord
204+
return AuthenticationRecord.deserialize(json);
205+
}
174206
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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 VisualStudioCodeCredentialTest {
11+
@Test
12+
public void testInValidStateVsCode() {
13+
// setup
14+
TokenRequestContext request
15+
= new TokenRequestContext().addScopes("https://vault.azure.net/.default").setTenantId("newTenant");
16+
17+
VisualStudioCodeCredential credential = new VisualStudioCodeCredentialBuilder().tenantId("tenant").build();
18+
StepVerifier.create(credential.getToken(request))
19+
.expectErrorMatches(e -> e instanceof CredentialUnavailableException
20+
&& (e.getMessage().startsWith("Visual Studio Code Authentication is not available. Ensure you have")))
21+
.verify();
22+
}
23+
24+
}

0 commit comments

Comments
 (0)