-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[core-http] Token refresher fix: Refresh in the background, keep returning valid token #14140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b03508b
d053a23
d31ba04
6cf21f1
da157c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| import { TokenCredential, GetTokenOptions } from "@azure/core-auth"; | ||
| import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-auth"; | ||
| import { | ||
| BaseRequestPolicy, | ||
| RequestPolicy, | ||
|
|
@@ -54,6 +54,8 @@ export function bearerTokenAuthenticationPolicy( | |
| * | ||
| */ | ||
| export class BearerTokenAuthenticationPolicy extends BaseRequestPolicy { | ||
| private token: AccessToken | undefined; | ||
|
|
||
| /** | ||
| * Creates a new BearerTokenAuthenticationPolicy object. | ||
| * | ||
|
|
@@ -83,34 +85,36 @@ export class BearerTokenAuthenticationPolicy extends BaseRequestPolicy { | |
| spanOptions: webResource.spanOptions | ||
| } | ||
| }); | ||
| webResource.headers.set(Constants.HeaderConstants.AUTHORIZATION, `Bearer ${token}`); | ||
| if (token) { | ||
| webResource.headers.set(Constants.HeaderConstants.AUTHORIZATION, `Bearer ${token}`); | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This reminds me to a feedback you (@xirzec) gave me earlier. Why do we set the bearer token if we don't have a token? This seems like an improvement. |
||
| return this._nextPolicy.sendRequest(webResource); | ||
| } | ||
|
|
||
| /** | ||
| * Attempts a token update if any other time related conditionals have been reached based on the tokenRefresher class. | ||
| */ | ||
| private async updateTokenIfNeeded(options: GetTokenOptions): Promise<void> { | ||
| if (this.tokenRefresher.isReady()) { | ||
| const accessToken = await this.tokenRefresher.refresh(options); | ||
| this.tokenCache.setCachedToken(accessToken); | ||
| private async getToken(options: GetTokenOptions): Promise<string | undefined> { | ||
| // We reset the cached token in a time window before it expires, | ||
| // after that point, we retry the refresh of the token only if the token refresher is ready. | ||
|
|
||
| let token = this.tokenCache.getCachedToken(); | ||
| let refreshPromise: Promise<AccessToken | undefined> | undefined; | ||
| if (!token && this.tokenRefresher.isReady()) { | ||
| refreshPromise = this.tokenRefresher.refresh(options); | ||
| } | ||
| } | ||
|
|
||
| private async getToken(options: GetTokenOptions): Promise<string | undefined> { | ||
| let accessToken = this.tokenCache.getCachedToken(); | ||
| if (accessToken === undefined) { | ||
| // Waiting for the next refresh only if the cache is unable to retrieve the access token, | ||
| // which means that it has expired, or it has never been set. | ||
| accessToken = await this.tokenRefresher.refresh(options); | ||
| this.tokenCache.setCachedToken(accessToken); | ||
| } else { | ||
| // If we still have a cached access token, | ||
| // And any other time related conditionals have been reached based on the tokenRefresher class, | ||
| // then attempt to refresh without waiting. | ||
| this.updateTokenIfNeeded(options); | ||
| // We don't use the local copy if the token cache has a token that has an expiration date greater than the local copy. | ||
| if (this.token && this.token.expiresOnTimestamp > (token?.expiresOnTimestamp || 0)) { | ||
| token = this.token; | ||
| } | ||
|
|
||
| return accessToken ? accessToken.token : undefined; | ||
| // If we don't have a token, or if the token has expired, we wait for the refresh promise | ||
| if (!token || token.expiresOnTimestamp < Date.now()) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add a buffer of time here? If the token is within 100ms of expiring, then there's a decent chance we won't get the request out the door and through the entire pipeline to validation at the service before it expires. Maybe we can say "if the token is within a second of expiring, then we wait."
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As long as the refresh window is not < 100ms, then there's no need for a buffer. We currently we default the refresh window to 2 minutes, and the refresh interval to 30 seconds.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about this case:
Since the token from the cache is valid,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be solved by the conditional // We don't use the local copy if the token cache has a token that has an expiration date greater than the local copy.
if (this.token && this.token.expiresOnTimestamp > (token?.expiresOnTimestamp || 0)) {
token = this.token;
}I know it's not pretty! But I'll push it while we come up with something better. |
||
| token = await refreshPromise; | ||
| } | ||
|
|
||
| // We save the token we were able to retrieve. | ||
| this.tokenCache.setCachedToken(token); | ||
| this.token = token; | ||
|
|
||
| return token ? token.token : undefined; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We didn't have this promise check. But there's no reason we should prevent refreshes if the promise exists, since we do re-use the promise 😁