Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support web apps for oauth based credentials #13151

Merged
merged 15 commits into from
Jul 16, 2020
Merged
2 changes: 1 addition & 1 deletion sdk/identity/azure-identity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Release History

## 1.1.0-beta.7 (Unreleased)

- Added support for web apps (confidential apps) for `InteractiveBrowserCredential` and `AuthorizationCodeCredential`. A client secret is required on the builder for web apps.

## 1.1.0-beta.6 (2020-07-10)
- Added `.getCredentials()` method to `DefaultAzureCredential` and `ChainedTokenCredential` and added option `.addAll(Collection<? extends TokenCredential>)` on `ChainedtokenCredentialBuilder`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,18 @@ public class AuthorizationCodeCredential implements TokenCredential {
* Creates an AuthorizationCodeCredential with the given identity client options.
*
* @param clientId the client ID of the application
* @param clientSecret the client secret of the application
* @param tenantId the tenant ID of the application
* @param authCode the Oauth 2.0 authorization code grant
* @param redirectUri the redirect URI used to authenticate to Azure Active Directory
* @param identityClientOptions the options for configuring the identity client
*/
AuthorizationCodeCredential(String clientId, String tenantId, String authCode, URI redirectUri,
IdentityClientOptions identityClientOptions) {
AuthorizationCodeCredential(String clientId, String clientSecret, String tenantId, String authCode,
URI redirectUri, IdentityClientOptions identityClientOptions) {
identityClient = new IdentityClientBuilder()
.tenantId(tenantId)
.clientId(clientId)
.clientSecret(clientSecret)
.identityClientOptions(identityClientOptions)
.build();
this.cachedToken = new AtomicReference<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class AuthorizationCodeCredentialBuilder extends AadCredentialBuilderBase

private String authCode;
private String redirectUrl;
private String clientSecret;

/**
* Sets the authorization code on the builder.
Expand Down Expand Up @@ -60,6 +61,16 @@ public AuthorizationCodeCredentialBuilder allowUnencryptedCache(boolean allowUne
return this;
}

/**
* Sets the client secret for the authentication.
* @param clientSecret the secret value of the AAD application.
* @return the AuthorizationCodeCredentialBuilder itself
*/
public AuthorizationCodeCredentialBuilder clientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}

/**
* Sets whether to enable using the shared token cache. This is disabled by default.
*
Expand All @@ -84,8 +95,8 @@ public AuthorizationCodeCredential build() {
put("redirectUrl", redirectUrl);
}});
try {
return new AuthorizationCodeCredential(clientId, tenantId, authCode,
new URI(redirectUrl), identityClientOptions);
return new AuthorizationCodeCredential(clientId, clientSecret, tenantId, authCode, new URI(redirectUrl),
identityClientOptions);
} catch (URISyntaxException e) {
throw logger.logExceptionAsError(new RuntimeException(e));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,19 @@ public class InteractiveBrowserCredential implements TokenCredential {
* {@code http://localhost:{port}} must be registered as a valid reply URL on the application.
*
* @param clientId the client ID of the application
* @param clientSecret the client secret of the application
* @param tenantId the tenant ID of the application
* @param port the port on which the credential will listen for the browser authentication result
* @param automaticAuthentication indicates whether automatic authentication should be attempted or not.
* @param identityClientOptions the options for configuring the identity client
*/
InteractiveBrowserCredential(String clientId, String tenantId, int port, boolean automaticAuthentication,
IdentityClientOptions identityClientOptions) {
String clientSecret, IdentityClientOptions identityClientOptions) {
this.port = port;
identityClient = new IdentityClientBuilder()
.tenantId(tenantId)
.clientId(clientId)
.clientSecret(clientSecret)
.identityClientOptions(identityClientOptions)
.build();
cachedToken = new AtomicReference<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
public class InteractiveBrowserCredentialBuilder extends AadCredentialBuilderBase<InteractiveBrowserCredentialBuilder> {
private int port;
private boolean automaticAuthentication = true;
private String clientSecret;

/**
* Sets the port for the local HTTP server, for which {@code http://localhost:{port}} must be
Expand Down Expand Up @@ -67,6 +68,16 @@ public InteractiveBrowserCredentialBuilder authenticationRecord(AuthenticationRe
return this;
}

/**
* Sets the client secret for the authentication.
* @param clientSecret the secret value of the AAD application.
* @return the InteractiveBrowserCredentialBuilder itself
*/
public InteractiveBrowserCredentialBuilder clientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}

/**
* Disables the automatic authentication and prevents the {@link InteractiveBrowserCredential} from automatically
* prompting the user. If automatic authentication is disabled a {@link AuthenticationRequiredException}
Expand All @@ -82,7 +93,6 @@ public InteractiveBrowserCredentialBuilder disableAutomaticAuthentication() {
return this;
}


/**
* Creates a new {@link InteractiveBrowserCredential} with the current configurations.
*
Expand All @@ -93,7 +103,7 @@ public InteractiveBrowserCredential build() {
put("clientId", clientId);
put("port", port);
}});
return new InteractiveBrowserCredential(clientId, tenantId, port, automaticAuthentication,
return new InteractiveBrowserCredential(clientId, tenantId, port, automaticAuthentication, clientSecret,
identityClientOptions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -550,13 +550,19 @@ public Mono<MsalToken> authenticateWithVsCodeCredential(TokenRequestContext requ
*/
public Mono<MsalToken> authenticateWithAuthorizationCode(TokenRequestContext request, String authorizationCode,
URI redirectUrl) {
return publicClientApplicationAccessor.getValue()
.flatMap(pc -> Mono.fromFuture(() -> pc.acquireToken(
AuthorizationCodeParameters.builder(authorizationCode, redirectUrl)
.scopes(new HashSet<>(request.getScopes()))
.build()))
.onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with authorization code",
null, t)).map(ar -> new MsalToken(ar, options)));
AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder(authorizationCode, redirectUrl)
.scopes(new HashSet<>(request.getScopes()))
.build();
Mono<IAuthenticationResult> acquireToken;
if (clientSecret != null) {
Copy link
Member

Choose a reason for hiding this comment

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

Where are you setting this client secret in ConfidentialClient ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

per offline discussion, this is set in the constructor of the IdentityClient.

acquireToken = confidentialClientApplicationAccessor.getValue()
.flatMap(pc -> Mono.fromFuture(() -> pc.acquireToken(parameters)));
} else {
acquireToken = publicClientApplicationAccessor.getValue()
.flatMap(pc -> Mono.fromFuture(() -> pc.acquireToken(parameters)));
}
return acquireToken.onErrorMap(t -> new ClientAuthenticationException(
"Failed to acquire token with authorization code", null, t)).map(ar -> new MsalToken(ar, options));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ public IdentityClientBuilder certificatePassword(String certificatePassword) {
return this;
}


/**
* Sets the options for the client.
* @param identityClientOptions the options for the client.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,19 @@ public void testValidSecretsWithTokenRefreshOffset() throws Exception {

// test
ClientSecretCredential credential = new ClientSecretCredentialBuilder()
.tenantId(tenantId)
.clientId(clientId)
.clientSecret(secret)
.tokenRefreshOffset(offset)
.build();
.tenantId(tenantId)
.clientId(clientId)
.clientSecret(secret)
.tokenRefreshOffset(offset)
.build();
StepVerifier.create(credential.getToken(request1))
.expectNextMatches(accessToken -> token1.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
.expectNextMatches(accessToken -> token1.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
StepVerifier.create(credential.getToken(request2))
.expectNextMatches(accessToken -> token2.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
.expectNextMatches(accessToken -> token2.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ public void testUseEnvironmentCredential() throws Exception {

IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class);
when(intelliJCredential.getToken(request1))
.thenReturn(Mono.empty());
.thenReturn(Mono.empty());
PowerMockito.whenNew(IntelliJCredential.class).withAnyArguments()
.thenReturn(intelliJCredential);
.thenReturn(intelliJCredential);

// test
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
Expand Down Expand Up @@ -88,9 +88,9 @@ public void testUseManagedIdentityCredential() throws Exception {

IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class);
when(intelliJCredential.getToken(request))
.thenReturn(Mono.empty());
.thenReturn(Mono.empty());
PowerMockito.whenNew(IntelliJCredential.class).withAnyArguments()
.thenReturn(intelliJCredential);
.thenReturn(intelliJCredential);

// test
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
Expand All @@ -110,9 +110,9 @@ public void testUseAzureCliCredential() throws Exception {
// mock
IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class);
when(intelliJCredential.getToken(request))
.thenReturn(Mono.empty());
.thenReturn(Mono.empty());
PowerMockito.whenNew(IntelliJCredential.class).withAnyArguments()
.thenReturn(intelliJCredential);
.thenReturn(intelliJCredential);

IdentityClient identityClient = PowerMockito.mock(IdentityClient.class);
when(identityClient.authenticateWithAzureCli(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
Expand Down Expand Up @@ -156,22 +156,22 @@ public void testNoCredentialWorks() throws Exception {

IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class);
when(intelliJCredential.getToken(request))
.thenReturn(Mono.error(
new CredentialUnavailableException("Cannot get token from IntelliJ Credential")));
.thenReturn(Mono.error(
new CredentialUnavailableException("Cannot get token from IntelliJ Credential")));
PowerMockito.whenNew(IntelliJCredential.class).withAnyArguments()
.thenReturn(intelliJCredential);
.thenReturn(intelliJCredential);

VisualStudioCodeCredential vscodeCredential = PowerMockito.mock(VisualStudioCodeCredential.class);
when(vscodeCredential.getToken(request))
.thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from VS Code credential")));
.thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from VS Code credential")));
PowerMockito.whenNew(VisualStudioCodeCredential.class).withAnyArguments()
.thenReturn(vscodeCredential);
.thenReturn(vscodeCredential);

// test
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
StepVerifier.create(credential.getToken(request))
.expectErrorMatches(t -> t instanceof CredentialUnavailableException && t.getMessage()
.startsWith("EnvironmentCredential authentication unavailable. "))
.startsWith("EnvironmentCredential authentication unavailable. "))
.verify();
}

Expand All @@ -190,22 +190,22 @@ public void testCredentialUnavailable() throws Exception {

IntelliJCredential intelliJCredential = PowerMockito.mock(IntelliJCredential.class);
when(intelliJCredential.getToken(request))
.thenReturn(Mono.error(
new CredentialUnavailableException("Cannot get token from IntelliJ Credential")));
.thenReturn(Mono.error(
new CredentialUnavailableException("Cannot get token from IntelliJ Credential")));
PowerMockito.whenNew(IntelliJCredential.class).withAnyArguments()
.thenReturn(intelliJCredential);
.thenReturn(intelliJCredential);
VisualStudioCodeCredential vscodeCredential = PowerMockito.mock(VisualStudioCodeCredential.class);
when(vscodeCredential.getToken(request))
.thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from VS Code credential")));
.thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from VS Code credential")));
PowerMockito.whenNew(VisualStudioCodeCredential.class).withAnyArguments()
.thenReturn(vscodeCredential);
.thenReturn(vscodeCredential);

// test
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder()
.build();
.build();
StepVerifier.create(credential.getToken(request))
.expectErrorMatches(t -> t instanceof CredentialUnavailableException && t.getMessage()
.startsWith("EnvironmentCredential authentication unavailable. "))
.startsWith("EnvironmentCredential authentication unavailable. "))
.verify();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public void testAuthorizationCodeFlow() throws Exception {
IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build();
StepVerifier.create(client.authenticateWithAuthorizationCode(request, authCode1, redirectUri))
.expectNextMatches(accessToken -> token1.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
}

Expand All @@ -267,7 +267,7 @@ public void testUserRefreshTokenflow() throws Exception {
IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build();
StepVerifier.create(client.authenticateWithPublicClientCache(request2, TestUtils.getMockMsalAccount(token1, expiresAt).block()))
.expectNextMatches(accessToken -> token2.equals(accessToken.getToken())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
&& expiresAt.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
}

Expand All @@ -288,7 +288,7 @@ public void testUsernamePasswordCodeFlow() throws Exception {
IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build();
StepVerifier.create(client.authenticateWithUsernamePassword(request, username, password))
.expectNextMatches(accessToken -> token.equals(accessToken.getToken())
&& expiresOn.getSecond() == accessToken.getExpiresAt().getSecond())
&& expiresOn.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
}

Expand All @@ -310,7 +310,7 @@ public void testBrowserAuthenicationCodeFlow() throws Exception {
IdentityClient client = new IdentityClientBuilder().tenantId(tenantId).clientId(clientId).identityClientOptions(options).build();
StepVerifier.create(client.authenticateWithBrowserInteraction(request, 4567))
.expectNextMatches(accessToken -> token.equals(accessToken.getToken())
&& expiresOn.getSecond() == accessToken.getExpiresAt().getSecond())
&& expiresOn.getSecond() == accessToken.getExpiresAt().getSecond())
.verifyComplete();
}

Expand Down