Skip to content

Commit b823793

Browse files
authored
Allow client assertion to be a callback and a per-request parameter (#482)
* Allow creating a client assertion from a callback * Allow client credentials as a per-request parameter * Minor changes to fix build issues
1 parent 42a5701 commit b823793

File tree

6 files changed

+90
-15
lines changed

6 files changed

+90
-15
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
<dependency>
102102
<groupId>com.azure</groupId>
103103
<artifactId>azure-security-keyvault-secrets</artifactId>
104-
<version>4.3.6</version>
104+
<version>4.3.5</version>
105105
<scope>test</scope>
106106
</dependency>
107107
<dependency>

src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.security.UnrecoverableKeyException;
1717
import java.security.cert.CertificateException;
1818
import java.util.Collections;
19+
import java.util.concurrent.Callable;
1920

2021
import static com.microsoft.aad.msal4j.TestConstants.KEYVAULT_DEFAULT_SCOPE;
2122

@@ -48,16 +49,35 @@ public void acquireTokenClientCredentials_ClientSecret() throws Exception {
4849
public void acquireTokenClientCredentials_ClientAssertion() throws Exception {
4950
String clientId = "2afb0add-2f32-4946-ac90-81a02aa4550e";
5051

51-
ClientAssertion clientAssertion = JwtHelper.buildJwt(
52-
clientId,
53-
(ClientCertificate) certificate,
54-
"https://login.microsoftonline.com/common/oauth2/v2.0/token",
55-
true);
52+
ClientAssertion clientAssertion = getClientAssertion(clientId);
53+
54+
IClientCredential credential = ClientCredentialFactory.createFromClientAssertion(clientAssertion.assertion());
55+
56+
assertAcquireTokenCommon(clientId, credential);
57+
}
58+
59+
@Test
60+
public void acquireTokenClientCredentials_Callback() throws Exception {
61+
String clientId = "2afb0add-2f32-4946-ac90-81a02aa4550e";
62+
63+
// Creates a valid client assertion using a callback, and uses it to build the client app and make a request
64+
Callable<String> callable = () -> {
65+
ClientAssertion clientAssertion = getClientAssertion(clientId);
5666

57-
IClientCredential credential = ClientCredentialFactory.createFromClientAssertion(
58-
clientAssertion.assertion());
67+
return clientAssertion.assertion();
68+
};
69+
70+
IClientCredential credential = ClientCredentialFactory.createFromCallback(callable);
5971

6072
assertAcquireTokenCommon(clientId, credential);
73+
74+
// Creates an invalid client assertion to build the application, but overrides it with a valid client assertion
75+
// in the request parameters in order to make a successful token request
76+
ClientAssertion invalidClientAssertion = getClientAssertion("abc");
77+
78+
IClientCredential invalidCredentials = ClientCredentialFactory.createFromClientAssertion(invalidClientAssertion.assertion());
79+
80+
assertAcquireTokenCommon_withParameters(clientId, invalidCredentials, credential);
6181
}
6282

6383
@Test
@@ -98,6 +118,13 @@ public void acquireTokenClientCredentials_DefaultCacheLookup() throws Exception
98118
Assert.assertNotEquals(result2.accessToken(), result3.accessToken());
99119
}
100120

121+
private ClientAssertion getClientAssertion(String clientId) {
122+
return JwtHelper.buildJwt(
123+
clientId,
124+
(ClientCertificate) certificate,
125+
"https://login.microsoftonline.com/common/oauth2/v2.0/token",
126+
true);
127+
}
101128

102129
private void assertAcquireTokenCommon(String clientId, IClientCredential credential) throws Exception {
103130
ConfidentialClientApplication cca = ConfidentialClientApplication.builder(
@@ -113,4 +140,20 @@ private void assertAcquireTokenCommon(String clientId, IClientCredential credent
113140
Assert.assertNotNull(result);
114141
Assert.assertNotNull(result.accessToken());
115142
}
143+
144+
private void assertAcquireTokenCommon_withParameters(String clientId, IClientCredential credential, IClientCredential credentialParam) throws Exception {
145+
146+
ConfidentialClientApplication cca = ConfidentialClientApplication.builder(
147+
clientId, credential).
148+
authority(TestConstants.MICROSOFT_AUTHORITY).
149+
build();
150+
151+
IAuthenticationResult result = cca.acquireToken(ClientCredentialParameters
152+
.builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE)).clientCredential(credentialParam)
153+
.build())
154+
.get();
155+
156+
Assert.assertNotNull(result);
157+
Assert.assertNotNull(result.accessToken());
158+
}
116159
}

src/main/java/com/microsoft/aad/msal4j/ClientCredentialFactory.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
import java.security.cert.CertificateException;
1010
import java.security.cert.X509Certificate;
1111
import java.util.List;
12+
import java.util.concurrent.Callable;
13+
import java.util.concurrent.ExecutionException;
14+
import java.util.concurrent.ExecutorService;
15+
import java.util.concurrent.Executors;
16+
import java.util.concurrent.Future;
1217

1318
import static com.microsoft.aad.msal4j.ParameterValidationUtils.validateNotNull;
1419

@@ -29,7 +34,7 @@ public static IClientSecret createFromSecret(String secret) {
2934
}
3035

3136
/**
32-
* Static method to create a {@link ClientCertificate} instance from a certificate
37+
* Static method to create a {@link ClientCertificate} instance from a password-protected certificate.
3338
*
3439
* @param pkcs12Certificate InputStream containing PCKS12 formatted certificate
3540
* @param password certificate password
@@ -48,7 +53,7 @@ public static IClientCertificate createFromCertificate(final InputStream pkcs12C
4853
}
4954

5055
/**
51-
* Static method to create a {@link ClientCertificate} instance.
56+
* Static method to create a {@link ClientCertificate} instance from a private key/public certificate pair.
5257
*
5358
* @param key RSA private key to sign the assertion.
5459
* @param publicKeyCertificate x509 public certificate used for thumbprint
@@ -61,7 +66,7 @@ public static IClientCertificate createFromCertificate(final PrivateKey key, fin
6166
}
6267

6368
/**
64-
* Static method to create a {@link ClientCertificate} instance.
69+
* Static method to create a {@link ClientCertificate} instance from a certificate chain.
6570
*
6671
* @param key RSA private key to sign the assertion.
6772
* @param publicKeyCertificateChain ordered with the user's certificate first followed by zero or more certificate authorities
@@ -75,12 +80,26 @@ public static IClientCertificate createFromCertificateChain(PrivateKey key, List
7580
}
7681

7782
/**
78-
* Static method to create a {@link ClientAssertion} instance.
83+
* Static method to create a {@link ClientAssertion} instance from a JWT token encoded as a base64 URL encoded string.
7984
*
80-
* @param clientAssertion Jwt token encoded as a base64 URL encoded string
85+
* @param clientAssertion JWT token encoded as a base64 URL encoded string
8186
* @return {@link ClientAssertion}
8287
*/
8388
public static IClientAssertion createFromClientAssertion(String clientAssertion) {
8489
return new ClientAssertion(clientAssertion);
8590
}
91+
92+
/**
93+
* Static method to create a {@link ClientAssertion} instance from a provided Callable.
94+
*
95+
* @param callable Callable that produces a JWT token encoded as a base64 URL encoded string
96+
* @return {@link ClientAssertion}
97+
*/
98+
public static IClientAssertion createFromCallback(Callable<String> callable) throws ExecutionException, InterruptedException {
99+
ExecutorService executor = Executors.newSingleThreadExecutor();
100+
101+
Future<String> future = executor.submit(callable);
102+
103+
return new ClientAssertion(future.get());
104+
}
86105
}

src/main/java/com/microsoft/aad/msal4j/ClientCredentialParameters.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ public class ClientCredentialParameters implements IAcquireTokenParameters {
4949
*/
5050
private String tenant;
5151

52+
/**
53+
* Overrides the client credentials for this request
54+
*/
55+
private IClientCredential clientCredential;
56+
5257
private static ClientCredentialParametersBuilder builder() {
5358

5459
return new ClientCredentialParametersBuilder();

src/main/java/com/microsoft/aad/msal4j/ConfidentialClientApplication.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ private ClientAuthentication buildValidClientCertificateAuthority() {
125125
return createClientAuthFromClientAssertion(clientAssertion);
126126
}
127127

128-
private ClientAuthentication createClientAuthFromClientAssertion(
128+
protected ClientAuthentication createClientAuthFromClientAssertion(
129129
final ClientAssertion clientAssertion) {
130130
final Map<String, List<String>> map = new HashMap<>();
131131
try {

src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,15 @@ OAuthHttpRequest createOauthHttpRequest() throws SerializeException, MalformedUR
7373
oauthHttpRequest.setQuery(URLUtils.serializeParameters(params));
7474

7575
if (msalRequest.application().clientAuthentication() != null) {
76-
msalRequest.application().clientAuthentication().applyTo(oauthHttpRequest);
76+
// If the client application has a client assertion to apply to the request, check if a new client assertion
77+
// was supplied as a request parameter. If so, use the request's assertion instead of the application's
78+
if (msalRequest instanceof ClientCredentialRequest && ((ClientCredentialRequest) msalRequest).parameters.clientCredential() != null) {
79+
((ConfidentialClientApplication) msalRequest.application())
80+
.createClientAuthFromClientAssertion((ClientAssertion) ((ClientCredentialRequest) msalRequest).parameters.clientCredential())
81+
.applyTo(oauthHttpRequest);
82+
} else {
83+
msalRequest.application().clientAuthentication().applyTo(oauthHttpRequest);
84+
}
7785
}
7886
return oauthHttpRequest;
7987
}

0 commit comments

Comments
 (0)