Skip to content

Commit 0648d17

Browse files
committed
Infer refresh_in in AppTokenProvider
1 parent eeb136b commit 0648d17

File tree

2 files changed

+82
-44
lines changed

2 files changed

+82
-44
lines changed

Diff for: lib/msal-node/src/client/ClientCredentialClient.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,18 @@ export class ClientCredentialClient extends BaseClient {
158158
appTokenPropviderParameters
159159
);
160160

161+
let refershIn = appTokenProviderResult.refreshInSeconds;
162+
if (!refershIn && appTokenProviderResult.expiresInSeconds > 7200) {
163+
164+
165+
refershIn = Math.round(appTokenProviderResult.expiresInSeconds /2);
166+
this.logger.info("Setting refresh in to " + refershIn + " seconds.");
167+
}
168+
161169
serverTokenResponse = {
162170
access_token: appTokenProviderResult.accessToken,
163171
expires_in: appTokenProviderResult.expiresInSeconds,
164-
refresh_in: appTokenProviderResult.refreshInSeconds,
172+
refresh_in: refershIn,
165173
token_type: AuthenticationScheme.BEARER,
166174
};
167175
} else {

Diff for: lib/msal-node/test/client/ClientCredentialClient.spec.ts

+73-43
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
ThrottlingConstants,
2121
TimeUtils,
2222
} from "@azure/msal-common";
23-
import { ClientCredentialClient, UsernamePasswordClient } from "../../src";
23+
import { AppTokenProviderParameters, ClientCredentialClient, UsernamePasswordClient } from "../../src";
2424
import {
2525
AUTHENTICATION_RESULT_DEFAULT_SCOPES,
2626
CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT,
@@ -1099,71 +1099,101 @@ describe("ClientCredentialClient unit tests", () => {
10991099
});
11001100

11011101
it("Uses the extensibility AppTokenProvider callback to get a token", async () => {
1102-
sinon
1103-
.stub(Authority.prototype, <any>"getEndpointMetadataFromNetwork")
1104-
.resolves(DEFAULT_OPENID_CONFIG_RESPONSE.body);
1105-
// no need to stub out the token response, MSAL will use the AppTokenProvider instead
1102+
await runAppTokenProviderTestAsync(3600, 500, 500); // refresh in is read from the AppTokenProvider
1103+
});
1104+
1105+
it("AppTokenProvider sets a refresh in to half of expires in", async () => {
1106+
await runAppTokenProviderTestAsync(7200, undefined, 3600); // refresh in is inferred as 1/2 expires in
1107+
1108+
1109+
});
1110+
it("AppTokenProvider does not set refresh in if expires in < 7200", async () => {
1111+
await runAppTokenProviderTestAsync(7000, undefined, undefined); // refresh in not inferred because expires in < 7200
1112+
});
11061113

1114+
function validateAppTokenProvider(config: ClientConfiguration, authResult: AuthenticationResult, expectedExpiresIn: number, expectedRefreshIn: number | undefined) {
1115+
1116+
expect(authResult.scopes).toEqual([TEST_CONFIG.DEFAULT_GRAPH_SCOPE[0]]);
1117+
expect(authResult.accessToken).toEqual("some_token");
1118+
expect(authResult.state).toHaveLength(0);
1119+
1120+
const actualExpiresIn = (authResult.expiresOn!.valueOf() - Date.now().valueOf()) / 1000;
1121+
validateDuration(actualExpiresIn, expectedExpiresIn!);
1122+
1123+
1124+
const accessTokenKey = config.storageInterface.getKeys();
1125+
1126+
// const accessTokenKey = config.storageInterface?.getKeys().find(value => value.indexOf("accesstoken") >= 0);
1127+
const accessTokenCacheItem = accessTokenKey ? config.storageInterface?.getAccessTokenCredential(accessTokenKey) : null;
1128+
expect(accessTokenCacheItem).not.toBeNull();
1129+
expect(accessTokenCacheItem?.refreshOn).not.toBeNull();
1130+
1131+
if (expectedRefreshIn == undefined) {
1132+
expect(accessTokenCacheItem?.refreshOn).toBeUndefined();
1133+
} else {
1134+
const refreshOnUnixTimestamp = Number(accessTokenCacheItem?.refreshOn) * 1000;
1135+
const refreshOnDate = new Date(refreshOnUnixTimestamp);
1136+
const refreshOnDiff = (refreshOnDate.valueOf() - Date.now().valueOf()) / 1000;
1137+
validateDuration(refreshOnDiff, expectedRefreshIn!);
1138+
}
1139+
}
1140+
1141+
function validateDuration(actualDuration: number, expectedDuration: number) {
1142+
// small buffer for test runtime differences
1143+
expect(actualDuration).toBeLessThanOrEqual(expectedDuration + 10);
1144+
expect(actualDuration).toBeGreaterThan(expectedDuration - 10);
1145+
}
1146+
1147+
async function runAppTokenProviderTestAsync(actualExpiresIn: number, actualRefreshIn: number | undefined, expectedRefreshIn: number | undefined) {
1148+
sinon.stub(Authority.prototype, <any>"getEndpointMetadataFromNetwork").resolves(DEFAULT_OPENID_CONFIG_RESPONSE.body);
1149+
// no need to stub out the token response, MSAL will use the AppTokenProvider instead
1150+
const config = await ClientTestUtils.createTestClientConfiguration();
11071151
const accessToken = "some_token";
11081152
const appTokenProviderResult: AppTokenProviderResult = {
11091153
accessToken: accessToken,
1110-
expiresInSeconds: 1800,
1111-
refreshInSeconds: 900,
1154+
expiresInSeconds: actualExpiresIn,
1155+
refreshInSeconds: actualRefreshIn,
11121156
};
1113-
1157+
11141158
const expectedScopes = [TEST_CONFIG.DEFAULT_GRAPH_SCOPE[0]];
1115-
1159+
11161160
let callbackedCalledCount = 0;
1117-
1118-
const appTokenProvider: IAppTokenProvider = (
1119-
appTokenProviderParameters
1120-
) => {
1161+
1162+
const appTokenProvider: IAppTokenProvider = (appTokenProviderParameters: AppTokenProviderParameters) => {
1163+
11211164
callbackedCalledCount++;
1122-
1165+
11231166
expect(appTokenProviderParameters.scopes).toEqual(expectedScopes);
11241167
expect(appTokenProviderParameters.tenantId).toEqual("common");
1125-
expect(appTokenProviderParameters.correlationId).toEqual(
1126-
TEST_CONFIG.CORRELATION_ID
1127-
);
1168+
expect(appTokenProviderParameters.correlationId).toEqual(TEST_CONFIG.CORRELATION_ID);
11281169
expect(appTokenProviderParameters.claims).toBeUndefined();
1129-
1130-
return new Promise<AppTokenProviderResult>((resolve) =>
1131-
resolve(appTokenProviderResult)
1132-
);
1170+
1171+
return new Promise<AppTokenProviderResult>(
1172+
(resolve) => resolve(appTokenProviderResult));
11331173
};
1134-
1174+
11351175
// client credentials not needed
11361176
config.clientCredentials = undefined;
1137-
1177+
11381178
const client = new ClientCredentialClient(config, appTokenProvider);
11391179
const clientCredentialRequest: CommonClientCredentialRequest = {
11401180
authority: TEST_CONFIG.validAuthority,
11411181
correlationId: TEST_CONFIG.CORRELATION_ID,
11421182
scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE,
11431183
};
1144-
1145-
const authResult = (await client.acquireToken(
1146-
clientCredentialRequest
1147-
)) as AuthenticationResult;
1148-
1184+
1185+
const authResult = await client.acquireToken(clientCredentialRequest) as AuthenticationResult;
1186+
11491187
expect(callbackedCalledCount).toEqual(1);
1150-
1151-
expect(authResult.scopes).toEqual(expectedScopes);
1152-
expect(authResult.accessToken).toEqual(accessToken);
1153-
expect(authResult.state).toHaveLength(0);
1154-
const dateDiff =
1155-
(authResult.expiresOn!.valueOf() - Date.now().valueOf()) / 1000;
1156-
expect(dateDiff).toBeLessThanOrEqual(1900);
1157-
expect(dateDiff).toBeGreaterThan(1700);
1158-
1159-
const authResult2 = (await client.acquireToken(
1160-
clientCredentialRequest
1161-
)) as AuthenticationResult;
1162-
1188+
1189+
validateAppTokenProvider(config, authResult, actualExpiresIn, expectedRefreshIn);
1190+
1191+
const authResult2 = await client.acquireToken(clientCredentialRequest) as AuthenticationResult;
1192+
11631193
// expect the callback to not be called again, because token comes from the cache
11641194
expect(callbackedCalledCount).toEqual(1);
1165-
1195+
11661196
expect(authResult2.scopes).toEqual(expectedScopes);
11671197
expect(authResult2.accessToken).toEqual(accessToken);
1168-
});
1198+
}
11691199
});

0 commit comments

Comments
 (0)