Skip to content

Commit 7ebd6d5

Browse files
committed
Add code for cache refresh in managed identity flow
1 parent a6e0fd4 commit 7ebd6d5

File tree

3 files changed

+103
-29
lines changed

3 files changed

+103
-29
lines changed

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByManagedIdentitySupplier.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ AuthenticationResult execute() throws Exception {
3737
clientApplication.serviceBundle()
3838
);
3939

40+
CacheRefreshReason cacheRefreshReason = CacheRefreshReason.NOT_APPLICABLE;
41+
4042
if (!managedIdentityParameters.forceRefresh) {
4143
LOG.debug("ForceRefresh set to false. Attempting cache lookup");
4244

@@ -63,23 +65,37 @@ AuthenticationResult execute() throws Exception {
6365
this.clientApplication,
6466
silentRequest);
6567

66-
return supplier.execute();
68+
AuthenticationResult result = supplier.execute();
69+
cacheRefreshReason = SilentRequestHelper.NeedsRefresh(
70+
parameters,
71+
result,
72+
LOG);
73+
74+
// If the token does not need a refresh, return the cached token
75+
// Else refresh the token if it is either expired, proactively refreshable, or if the claims are passed.
76+
if (cacheRefreshReason == CacheRefreshReason.NOT_APPLICABLE) {
77+
LOG.debug("Returning token from cache");
78+
result.metadata().tokenSource(TokenSource.CACHE);
79+
return result;
80+
} else {
81+
LOG.debug(String.format("Refreshing access token. Cache refresh reason: %s", cacheRefreshReason));
82+
}
6783
} catch (MsalClientException ex) {
6884
if (ex.errorCode().equals(AuthenticationErrorCode.CACHE_MISS)) {
6985
LOG.debug(String.format("Cache lookup failed: %s", ex.getMessage()));
70-
return fetchNewAccessTokenAndSaveToCache(tokenRequestExecutor);
86+
return fetchNewAccessTokenAndSaveToCache(tokenRequestExecutor, cacheRefreshReason);
7187
} else {
7288
LOG.error(String.format("Error occurred while cache lookup: %s", ex.getMessage()));
7389
throw ex;
7490
}
7591
}
7692
}
7793

78-
LOG.info("Skipped looking for an Access Token in the cache because forceRefresh or Claims were set. ");
79-
return fetchNewAccessTokenAndSaveToCache(tokenRequestExecutor);
94+
LOG.info("Skipped looking for an Access Token in the cache because forceRefresh was set. Or the token in the cache needs refresh");
95+
return fetchNewAccessTokenAndSaveToCache(tokenRequestExecutor, cacheRefreshReason);
8096
}
8197

82-
private AuthenticationResult fetchNewAccessTokenAndSaveToCache(TokenRequestExecutor tokenRequestExecutor) {
98+
private AuthenticationResult fetchNewAccessTokenAndSaveToCache(TokenRequestExecutor tokenRequestExecutor, CacheRefreshReason cacheRefreshReason) throws Exception {
8399

84100
ManagedIdentityClient managedIdentityClient = new ManagedIdentityClient(msalRequest, tokenRequestExecutor.getServiceBundle());
85101

@@ -91,7 +107,10 @@ private AuthenticationResult fetchNewAccessTokenAndSaveToCache(TokenRequestExecu
91107

92108
AuthenticationResult authenticationResult = createFromManagedIdentityResponse(managedIdentityResponse);
93109
clientApplication.tokenCache.saveTokens(tokenRequestExecutor, authenticationResult, clientApplication.authenticationAuthority.host);
94-
return authenticationResult;
110+
AuthenticationResult result = authenticationResult;
111+
result.metadata().tokenSource(TokenSource.IDENTITY_PROVIDER);
112+
result.metadata().cacheRefreshReason(cacheRefreshReason);
113+
return result;
95114
}
96115

97116
private AuthenticationResult createFromManagedIdentityResponse(ManagedIdentityResponse managedIdentityResponse) {

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenSilentSupplier.java

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,34 +43,34 @@ AuthenticationResult execute() throws Exception {
4343
requestAuthority,
4444
silentRequest.parameters().scopes(),
4545
clientApplication.clientId());
46-
}
47-
48-
if (res == null) {
49-
throw new MsalClientException(AuthenticationErrorMessage.NO_TOKEN_IN_CACHE, AuthenticationErrorCode.CACHE_MISS);
50-
}
5146

52-
//Some cached tokens were found, but this metadata will be overwritten if token needs to be refreshed
53-
res.metadata().tokenSource(TokenSource.CACHE);
47+
if (res == null) {
48+
throw new MsalClientException(AuthenticationErrorMessage.NO_TOKEN_IN_CACHE, AuthenticationErrorCode.CACHE_MISS);
49+
}
5450

55-
if (!StringHelper.isBlank(res.accessToken())) {
56-
clientApplication.serviceBundle().getServerSideTelemetry().incrementSilentSuccessfulCount();
57-
}
51+
//Some cached tokens were found, but this metadata will be overwritten if token needs to be refreshed
52+
res.metadata().tokenSource(TokenSource.CACHE);
5853

59-
shouldRefresh = shouldRefresh(silentRequest.parameters(), res);
54+
if (!StringHelper.isBlank(res.accessToken())) {
55+
clientApplication.serviceBundle().getServerSideTelemetry().incrementSilentSuccessfulCount();
56+
}
6057

61-
if (shouldRefresh) {
62-
if (!StringHelper.isBlank(res.refreshToken())) {
63-
//There are certain scenarios where the cached authority may differ from the client app's authority,
64-
// such as when a request is instance aware. Unless overridden by SilentParameters.authorityUrl, the
65-
// cached authority should be used in the token refresh request
66-
if (silentRequest.parameters().authorityUrl() == null && !res.account().environment().equals(requestAuthority.host)) {
67-
requestAuthority = Authority.createAuthority(new URL(requestAuthority.authority().replace(requestAuthority.host(),
68-
res.account().environment())));
58+
shouldRefresh = shouldRefresh(silentRequest.parameters(), res);
59+
60+
if (shouldRefresh) {
61+
if (!StringHelper.isBlank(res.refreshToken())) {
62+
//There are certain scenarios where the cached authority may differ from the client app's authority,
63+
// such as when a request is instance aware. Unless overridden by SilentParameters.authorityUrl, the
64+
// cached authority should be used in the token refresh request
65+
if (silentRequest.parameters().authorityUrl() == null && !res.account().environment().equals(requestAuthority.host)) {
66+
requestAuthority = Authority.createAuthority(new URL(requestAuthority.authority().replace(requestAuthority.host(),
67+
res.account().environment())));
68+
}
69+
70+
res = makeRefreshRequest(res, requestAuthority, clientApplication.serviceBundle().getServerSideTelemetry().getCurrentRequest().cacheInfo());
71+
} else {
72+
res = null;
6973
}
70-
71-
res = makeRefreshRequest(res, requestAuthority, clientApplication.serviceBundle().getServerSideTelemetry().getCurrentRequest().cacheInfo());
72-
} else {
73-
res = null;
7474
}
7575
}
7676

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.aad.msal4j;
5+
6+
import java.util.Date;
7+
import org.slf4j.Logger;
8+
9+
class SilentRequestHelper {
10+
11+
private static final int ACCESS_TOKEN_EXPIRE_BUFFER_IN_SEC = 5 * 60;
12+
13+
private SilentRequestHelper() {
14+
// Utility class
15+
}
16+
17+
static CacheRefreshReason NeedsRefresh(SilentParameters parameters, AuthenticationResult cachedResult, Logger log) {
18+
//If forceRefresh is true, no reason to check any other option
19+
if (parameters.forceRefresh()) {
20+
log.debug(String.format("Refreshing access token. Cache refresh reason: %s", CacheRefreshReason.FORCE_REFRESH));
21+
return CacheRefreshReason.FORCE_REFRESH;
22+
}
23+
24+
//If the request contains claims then the token should be refreshed, to ensure that the returned token has the correct claims
25+
// Note: these are the types of claims found in (for example) a claims challenge, and do not include client capabilities
26+
if (parameters.claims() != null) {
27+
log.debug(String.format("Refreshing access token. Cache refresh reason: %s", CacheRefreshReason.CLAIMS));
28+
return CacheRefreshReason.CLAIMS;
29+
}
30+
31+
long currTimeStampSec = new Date().getTime() / 1000;
32+
33+
//If the access token is expired or within 5 minutes of becoming expired, refresh it
34+
if (!StringHelper.isBlank(cachedResult.accessToken()) && cachedResult.expiresOn() < (currTimeStampSec + ACCESS_TOKEN_EXPIRE_BUFFER_IN_SEC)) {
35+
log.debug(String.format("Refreshing access token. Cache refresh reason: %s", CacheRefreshReason.EXPIRED));
36+
return CacheRefreshReason.EXPIRED;
37+
}
38+
39+
//Certain long-lived tokens will have a 'refresh on' time that indicates a refresh should be attempted long before the token would expire
40+
if (!StringHelper.isBlank(cachedResult.accessToken()) &&
41+
cachedResult.refreshOn() != null && cachedResult.refreshOn() > 0 &&
42+
cachedResult.refreshOn() < currTimeStampSec && cachedResult.expiresOn() >= (currTimeStampSec + ACCESS_TOKEN_EXPIRE_BUFFER_IN_SEC)){
43+
log.debug(String.format("Refreshing access token. Cache refresh reason: %s", CacheRefreshReason.PROACTIVE_REFRESH));
44+
return CacheRefreshReason.PROACTIVE_REFRESH;
45+
}
46+
47+
//If there is a refresh token but no access token, we should use the refresh token to get the access token
48+
if (StringHelper.isBlank(cachedResult.accessToken()) && !StringHelper.isBlank(cachedResult.refreshToken())) {
49+
log.debug(String.format("Refreshing access token. Cache refresh reason: %s", CacheRefreshReason.NO_CACHED_ACCESS_TOKEN));
50+
return CacheRefreshReason.NO_CACHED_ACCESS_TOKEN;
51+
}
52+
53+
return CacheRefreshReason.NOT_APPLICABLE;
54+
}
55+
}

0 commit comments

Comments
 (0)