Skip to content

Commit 7962694

Browse files
authored
Revert "[core-http] Token refresher fix (Azure#13736)" (Azure#14154)
This reverts commit 849472a.
1 parent a8bb978 commit 7962694

File tree

5 files changed

+56
-96
lines changed

5 files changed

+56
-96
lines changed

sdk/core/core-http/CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
## 1.2.4 (Unreleased)
44

5-
- Updated the `bearerTokenAuthenticationPolicy` to refresh tokens only when they're about to expire and not multiple times before. This fixes the issue: [13369](https://github.com/Azure/azure-sdk-for-js/issues/13369).
65

76
## 1.2.3 (2021-02-04)
87

sdk/core/core-http/karma.conf.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const defaults = {
44

55
process.env.CHROME_BIN = require("puppeteer").executablePath();
66

7-
module.exports = function (config: any) {
7+
module.exports = function(config: any) {
88
config.set({
99
plugins: [
1010
"karma-mocha",

sdk/core/core-http/src/credentials/accessTokenRefresher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class AccessTokenRefresher {
2323
public isReady(): boolean {
2424
// We're only ready for a new refresh if the required milliseconds have passed.
2525
return (
26-
!this.lastCalled || Date.now() - this.lastCalled >= this.requiredMillisecondsBeforeNewRefresh
26+
!this.lastCalled || Date.now() - this.lastCalled > this.requiredMillisecondsBeforeNewRefresh
2727
);
2828
}
2929

sdk/core/core-http/src/policies/bearerTokenAuthenticationPolicy.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,30 @@ export class BearerTokenAuthenticationPolicy extends BaseRequestPolicy {
8787
return this._nextPolicy.sendRequest(webResource);
8888
}
8989

90+
/**
91+
* Attempts a token update if any other time related conditionals have been reached based on the tokenRefresher class.
92+
*/
93+
private async updateTokenIfNeeded(options: GetTokenOptions): Promise<void> {
94+
if (this.tokenRefresher.isReady()) {
95+
const accessToken = await this.tokenRefresher.refresh(options);
96+
this.tokenCache.setCachedToken(accessToken);
97+
}
98+
}
99+
90100
private async getToken(options: GetTokenOptions): Promise<string | undefined> {
91-
// We reset the cached token some before it expires,
92-
// after that point, we retry the refresh of the token only if the token refresher is ready.
93-
let token = this.tokenCache.getCachedToken();
94-
if (!token && this.tokenRefresher.isReady()) {
95-
token = await this.tokenRefresher.refresh(options);
96-
this.tokenCache.setCachedToken(token);
101+
let accessToken = this.tokenCache.getCachedToken();
102+
if (accessToken === undefined) {
103+
// Waiting for the next refresh only if the cache is unable to retrieve the access token,
104+
// which means that it has expired, or it has never been set.
105+
accessToken = await this.tokenRefresher.refresh(options);
106+
this.tokenCache.setCachedToken(accessToken);
107+
} else {
108+
// If we still have a cached access token,
109+
// And any other time related conditionals have been reached based on the tokenRefresher class,
110+
// then attempt to refresh without waiting.
111+
this.updateTokenIfNeeded(options);
97112
}
98-
return token ? token.token : undefined;
113+
114+
return accessToken ? accessToken.token : undefined;
99115
}
100116
}

sdk/core/core-http/test/policies/bearerTokenAuthenticationPolicyTests.ts

Lines changed: 31 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT license.
33

44
import { assert } from "chai";
5-
import Sinon, { fake, createSandbox } from "sinon";
5+
import { fake, createSandbox } from "sinon";
66
import { OperationSpec } from "../../src/operationSpec";
77
import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-auth";
88
import { RequestPolicy, RequestPolicyOptions } from "../../src/policies/requestPolicy";
@@ -11,15 +11,12 @@ import { HttpOperationResponse } from "../../src/httpOperationResponse";
1111
import { HttpHeaders } from "../../src/httpHeaders";
1212
import { WebResource } from "../../src/webResource";
1313
import { BearerTokenAuthenticationPolicy } from "../../src/policies/bearerTokenAuthenticationPolicy";
14-
import { ExpiringAccessTokenCache } from "../../src/credentials/accessTokenCache";
14+
import {
15+
ExpiringAccessTokenCache,
16+
TokenRefreshBufferMs
17+
} from "../../src/credentials/accessTokenCache";
1518
import { AccessTokenRefresher } from "../../src/credentials/accessTokenRefresher";
1619

17-
// Token is refreshed one millisecond BEFORE it expires.
18-
const beforeTokenExpiresInMs = 1000;
19-
20-
// To avoid many refresh requests, we make new refresh requests only after:
21-
const tokenRefreshIntervalInMs = 500;
22-
2320
describe("BearerTokenAuthenticationPolicy", function() {
2421
const mockPolicy: RequestPolicy = {
2522
sendRequest(request: WebResource): Promise<HttpOperationResponse> {
@@ -31,15 +28,6 @@ describe("BearerTokenAuthenticationPolicy", function() {
3128
}
3229
};
3330

34-
let clock: Sinon.SinonFakeTimers;
35-
36-
beforeEach(() => {
37-
clock = Sinon.useFakeTimers(Date.now());
38-
});
39-
afterEach(() => {
40-
clock.restore();
41-
});
42-
4331
it("correctly adds an Authentication header with the Bearer token", async function() {
4432
const mockToken = "token";
4533
const tokenScopes = ["scope1", "scope2"];
@@ -65,11 +53,32 @@ describe("BearerTokenAuthenticationPolicy", function() {
6553
);
6654
});
6755

56+
it("refreshes access tokens when they expire", async () => {
57+
const now = Date.now();
58+
const refreshCred1 = new MockRefreshAzureCredential(now);
59+
const refreshCred2 = new MockRefreshAzureCredential(now + TokenRefreshBufferMs);
60+
const notRefreshCred1 = new MockRefreshAzureCredential(now + TokenRefreshBufferMs + 5000);
61+
62+
const credentialsToTest: [MockRefreshAzureCredential, number, string][] = [
63+
[refreshCred1, 2, "Refresh credential 1"],
64+
[refreshCred2, 2, "Refresh credential 2"],
65+
[notRefreshCred1, 1, "Not refresh credential 1"]
66+
];
67+
68+
const request = createRequest();
69+
for (const [credentialToTest, expectedCalls, message] of credentialsToTest) {
70+
const policy = createBearerTokenPolicy("testscope", credentialToTest);
71+
await policy.sendRequest(request);
72+
await policy.sendRequest(request);
73+
assert.strictEqual(credentialToTest.authCount, expectedCalls, `${message} failed`);
74+
}
75+
});
76+
6877
it("tests that AccessTokenRefresher is working", async function() {
6978
const now = Date.now();
7079
const credentialToTest = new MockRefreshAzureCredential(now);
7180
const request = createRequest();
72-
const policy = createBearerTokenPolicy("test-scope", credentialToTest);
81+
const policy = createBearerTokenPolicy("testscope", credentialToTest);
7382
await policy.sendRequest(request);
7483

7584
const sandbox = createSandbox();
@@ -79,66 +88,6 @@ describe("BearerTokenAuthenticationPolicy", function() {
7988
assert.strictEqual(credentialToTest.authCount, 2);
8089
});
8190

82-
it("refreshes the token on initial request", async () => {
83-
const expiresOn = Date.now() + 1000 * 60; // One minute later.
84-
const credential = new MockRefreshAzureCredential(expiresOn);
85-
const request = createRequest();
86-
const policy = createBearerTokenPolicy("test-scope", credential);
87-
88-
await policy.sendRequest(request);
89-
assert.strictEqual(credential.authCount, 1);
90-
});
91-
92-
it("refreshes the token again only once it expired", async () => {
93-
const expireDelayMs = 5000;
94-
let tokenExpiration = Date.now() + expireDelayMs;
95-
const credential = new MockRefreshAzureCredential(tokenExpiration);
96-
const request = createRequest();
97-
const policy = createBearerTokenPolicy("test-scope", credential);
98-
99-
// The token is cached and remains cached for a bit.
100-
await policy.sendRequest(request);
101-
await policy.sendRequest(request);
102-
assert.strictEqual(credential.authCount, 1);
103-
104-
// The token will remain cached until tokenExpiration - testTokenRefreshBufferMs, so in (5000 - 1000) milliseconds.
105-
106-
// For safe measure, we test the token is still cached a second earlier than the refresh expectation.
107-
clock.tick(expireDelayMs - beforeTokenExpiresInMs - 1000);
108-
await policy.sendRequest(request);
109-
assert.strictEqual(credential.authCount, 1);
110-
111-
// The new token will last 5 seconds again.
112-
tokenExpiration = Date.now() + expireDelayMs;
113-
credential.setExpiresOnTimestamp(tokenExpiration);
114-
115-
// Now we wait until it expires:
116-
clock.tick(1000);
117-
await policy.sendRequest(request);
118-
assert.strictEqual(credential.authCount, 2);
119-
});
120-
121-
it("access token refresher should prevent refreshers to happen too fast", async () => {
122-
const expiresOn = Date.now(); // Already expired
123-
const credential = new MockRefreshAzureCredential(expiresOn);
124-
const request = createRequest();
125-
const policy = createBearerTokenPolicy("test-scope", credential);
126-
127-
await policy.sendRequest(request);
128-
assert.strictEqual(credential.authCount, 1);
129-
credential.setExpiresOnTimestamp(Date.now());
130-
131-
await policy.sendRequest(request);
132-
assert.strictEqual(credential.authCount, 1);
133-
credential.setExpiresOnTimestamp(Date.now());
134-
135-
clock.tick(tokenRefreshIntervalInMs);
136-
137-
await policy.sendRequest(request);
138-
assert.strictEqual(credential.authCount, 2);
139-
credential.setExpiresOnTimestamp(Date.now());
140-
});
141-
14291
function createRequest(operationSpec?: OperationSpec): WebResource {
14392
const request = new WebResource();
14493
request.operationSpec = operationSpec;
@@ -152,8 +101,8 @@ describe("BearerTokenAuthenticationPolicy", function() {
152101
return new BearerTokenAuthenticationPolicy(
153102
mockPolicy,
154103
new RequestPolicyOptions(),
155-
new ExpiringAccessTokenCache(beforeTokenExpiresInMs),
156-
new AccessTokenRefresher(credential, scopes, tokenRefreshIntervalInMs)
104+
new ExpiringAccessTokenCache(),
105+
new AccessTokenRefresher(credential, scopes)
157106
);
158107
}
159108
});
@@ -166,15 +115,11 @@ class MockRefreshAzureCredential implements TokenCredential {
166115
this._expiresOnTimestamp = expiresOnTimestamp;
167116
}
168117

169-
public setExpiresOnTimestamp(expiresOnTimestamp: number): void {
170-
this._expiresOnTimestamp = expiresOnTimestamp;
171-
}
172-
173-
public async getToken(
118+
public getToken(
174119
_scopes: string | string[],
175120
_options?: GetTokenOptions
176121
): Promise<AccessToken | null> {
177122
this.authCount++;
178-
return { token: "mock-token", expiresOnTimestamp: this._expiresOnTimestamp };
123+
return Promise.resolve({ token: "mocktoken", expiresOnTimestamp: this._expiresOnTimestamp });
179124
}
180125
}

0 commit comments

Comments
 (0)