Skip to content

Commit 6198e90

Browse files
authored
Add default cache lookup to client credential flow (#368)
* Add default cache lookup to client credential flow
1 parent af88e0b commit 6198e90

File tree

7 files changed

+126
-37
lines changed

7 files changed

+126
-37
lines changed

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,45 @@ public void acquireTokenClientCredentials_ClientAssertion() throws Exception{
6060
assertAcquireTokenCommon(clientId, credential);
6161
}
6262

63+
@Test
64+
public void acquireTokenClientCredentials_DefaultCacheLookup() throws Exception{
65+
AppCredentialProvider appProvider = new AppCredentialProvider(AzureEnvironment.AZURE);
66+
final String clientId = appProvider.getLabVaultAppId();
67+
final String password = appProvider.getLabVaultPassword();
68+
IClientCredential credential = ClientCredentialFactory.createFromSecret(password);
69+
70+
ConfidentialClientApplication cca = ConfidentialClientApplication.builder(
71+
clientId, credential).
72+
authority(TestConstants.MICROSOFT_AUTHORITY).
73+
build();
74+
75+
IAuthenticationResult result1 = cca.acquireToken(ClientCredentialParameters
76+
.builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE))
77+
.build())
78+
.get();
79+
80+
Assert.assertNotNull(result1);
81+
Assert.assertNotNull(result1.accessToken());
82+
83+
IAuthenticationResult result2 = cca.acquireToken(ClientCredentialParameters
84+
.builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE))
85+
.build())
86+
.get();
87+
88+
Assert.assertEquals(result1.accessToken(), result2.accessToken());
89+
90+
IAuthenticationResult result3 = cca.acquireToken(ClientCredentialParameters
91+
.builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE))
92+
.skipCache(true)
93+
.build())
94+
.get();
95+
96+
Assert.assertNotNull(result3);
97+
Assert.assertNotNull(result3.accessToken());
98+
Assert.assertNotEquals(result2.accessToken(), result3.accessToken());
99+
}
100+
101+
63102
private void assertAcquireTokenCommon(String clientId, IClientCredential credential) throws Exception{
64103
ConfidentialClientApplication cca = ConfidentialClientApplication.builder(
65104
clientId, credential).

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,14 @@ private AuthenticationResultSupplier getAuthenticationResultSupplier(MsalRequest
246246
(DeviceCodeFlowRequest) msalRequest);
247247
} else if (msalRequest instanceof SilentRequest) {
248248
supplier = new AcquireTokenSilentSupplier(this, (SilentRequest) msalRequest);
249-
} else if(msalRequest instanceof InteractiveRequest){
249+
} else if(msalRequest instanceof InteractiveRequest){
250250
supplier = new AcquireTokenByInteractiveFlowSupplier(
251251
(PublicClientApplication) this,
252252
(InteractiveRequest) msalRequest);
253+
} else if(msalRequest instanceof ClientCredentialRequest){
254+
supplier = new AcquireTokenByClientCredentialSupplier(
255+
(ConfidentialClientApplication) this,
256+
(ClientCredentialRequest) msalRequest);
253257
} else {
254258
supplier = new AcquireTokenByAuthorizationGrantSupplier(
255259
this,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.aad.msal4j;
5+
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
9+
public class AcquireTokenByClientCredentialSupplier extends AuthenticationResultSupplier {
10+
11+
private final static Logger LOG = LoggerFactory.getLogger(AcquireTokenByClientCredentialSupplier.class);
12+
private ClientCredentialRequest clientCredentialRequest;
13+
14+
AcquireTokenByClientCredentialSupplier(ConfidentialClientApplication clientApplication,
15+
ClientCredentialRequest clientCredentialRequest) {
16+
super(clientApplication, clientCredentialRequest);
17+
this.clientCredentialRequest = clientCredentialRequest;
18+
}
19+
20+
@Override
21+
AuthenticationResult execute() throws Exception {
22+
if (clientCredentialRequest.parameters.skipCache() != null &&
23+
!clientCredentialRequest.parameters.skipCache()) {
24+
LOG.info("SkipCache set to false. Attempting cache lookup");
25+
try {
26+
SilentParameters parameters = SilentParameters
27+
.builder(this.clientCredentialRequest.parameters.scopes())
28+
.claims(this.clientCredentialRequest.parameters.claims())
29+
.build();
30+
31+
SilentRequest silentRequest = new SilentRequest(
32+
parameters,
33+
this.clientApplication,
34+
this.clientApplication.createRequestContext(PublicApi.ACQUIRE_TOKEN_SILENTLY, parameters));
35+
36+
AcquireTokenSilentSupplier supplier = new AcquireTokenSilentSupplier(
37+
this.clientApplication,
38+
silentRequest);
39+
40+
return supplier.execute();
41+
} catch (MsalClientException ex) {
42+
LOG.debug(String.format("Cache lookup failed: %s", ex.getMessage()));
43+
return acquireTokenByClientCredential();
44+
}
45+
}
46+
47+
LOG.info("SkipCache set to true. Skipping cache lookup and attempting client credentials request");
48+
return acquireTokenByClientCredential();
49+
}
50+
51+
private AuthenticationResult acquireTokenByClientCredential() throws Exception {
52+
AcquireTokenByAuthorizationGrantSupplier supplier = new AcquireTokenByAuthorizationGrantSupplier(
53+
this.clientApplication,
54+
clientCredentialRequest,
55+
null);
56+
57+
return supplier.execute();
58+
}
59+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ public class ClientCredentialParameters implements IApiParameters {
2626
@NonNull
2727
private Set<String> scopes;
2828

29+
/**
30+
* Indicates whether the request should skip looking into the token cache. Be default it is
31+
* set to false.
32+
*/
33+
@Builder.Default
34+
private Boolean skipCache = false;
35+
2936
/**
3037
* Claims to be requested through the OIDC claims request parameter, allowing requests for standard and custom claims
3138
*/

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@
77

88
class ClientCredentialRequest extends MsalRequest{
99

10+
ClientCredentialParameters parameters;
11+
1012
ClientCredentialRequest(ClientCredentialParameters parameters,
1113
ConfidentialClientApplication application,
1214
RequestContext requestContext){
13-
super(application, createMsalGrant(parameters), requestContext );
15+
super(application, createMsalGrant(parameters), requestContext);
16+
this.parameters = parameters;
1417
}
1518

1619
private static OAuthAuthorizationGrant createMsalGrant(ClientCredentialParameters parameters){
17-
1820
return new OAuthAuthorizationGrant(new ClientCredentialsGrant(), parameters.scopes(), parameters.claims());
1921
}
2022
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ public interface IConfidentialClientApplication extends IClientApplicationBase {
2020

2121
/**
2222
* Acquires tokens from the authority configured in the application, for the confidential client
23-
* itself
23+
* itself. It will by default attempt to get tokens from the token cache. If no tokens are found,
24+
* it falls back to acquiring them via client credentials from the STS
2425
* @param parameters instance of {@link ClientCredentialParameters}
2526
* @return {@link CompletableFuture} containing an {@link IAuthenticationResult}
2627
*/

src/samples/confidential-client/ClientCredentialGrant.java

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
77
import com.microsoft.aad.msal4j.IAuthenticationResult;
88
import com.microsoft.aad.msal4j.IClientCredential;
9-
import com.microsoft.aad.msal4j.MsalException;
10-
import com.microsoft.aad.msal4j.SilentParameters;
119

1210
import java.util.Collections;
1311
import java.util.Set;
@@ -26,45 +24,24 @@ public static void main(String args[]) throws Exception {
2624

2725
private static IAuthenticationResult acquireToken() throws Exception {
2826

29-
// Load token cache from file and initialize token cache aspect. The token cache will have
30-
// dummy data, so the acquireTokenSilently call will fail.
31-
TokenCacheAspect tokenCacheAspect = new TokenCacheAspect("sample_cache.json");
32-
3327
// This is the secret that is created in the Azure portal when registering the application
3428
IClientCredential credential = ClientCredentialFactory.createFromSecret(CLIENT_SECRET);
3529
ConfidentialClientApplication cca =
3630
ConfidentialClientApplication
3731
.builder(CLIENT_ID, credential)
3832
.authority(AUTHORITY)
39-
.setTokenCacheAccessAspect(tokenCacheAspect)
4033
.build();
4134

42-
IAuthenticationResult result;
43-
try {
44-
SilentParameters silentParameters =
45-
SilentParameters
46-
.builder(SCOPE)
47-
.build();
48-
49-
// try to acquire token silently. This call will fail since the token cache does not
50-
// have a token for the application you are requesting an access token for
51-
result = cca.acquireTokenSilently(silentParameters).join();
52-
} catch (Exception ex) {
53-
if (ex.getCause() instanceof MsalException) {
54-
55-
ClientCredentialParameters parameters =
56-
ClientCredentialParameters
57-
.builder(SCOPE)
58-
.build();
35+
// Client credential requests will by default try to look for a valid token in the
36+
// in-memory token cache. If found, it will return this token. If a token is not found, or the
37+
// token is not valid, it will fall back to acquiring a token from the AAD service. Although
38+
// not recommended unless there is a reason for doing so, you can skip the cache lookup
39+
// by using .skipCache(true) in ClientCredentialParameters.
40+
ClientCredentialParameters parameters =
41+
ClientCredentialParameters
42+
.builder(SCOPE)
43+
.build();
5944

60-
// Try to acquire a token. If successful, you should see
61-
// the token information printed out to console
62-
result = cca.acquireToken(parameters).join();
63-
} else {
64-
// Handle other exceptions accordingly
65-
throw ex;
66-
}
67-
}
68-
return result;
45+
return cca.acquireToken(parameters).join();
6946
}
7047
}

0 commit comments

Comments
 (0)