Skip to content

[core-http] Rewrite bearerTokenAuthenticationPolicy#14223

Merged
18 commits merged intoAzure:masterfrom
witemple-msft:core-http/new-refresher
Mar 30, 2021
Merged

[core-http] Rewrite bearerTokenAuthenticationPolicy#14223
18 commits merged intoAzure:masterfrom
witemple-msft:core-http/new-refresher

Conversation

@witemple-msft
Copy link
Copy Markdown
Member

@witemple-msft witemple-msft commented Mar 10, 2021

This is a total reimplementation of bearerTokenAuthenticationPolicy. I'm proposing this because the existing implementation has some issues and relies on a class hierarchy that is unfortunately exposed through the core-http public API surface (and is therefore difficult to improve upon).

In this implementation, the architecture looks like this:

  • The bearerTokenAuthenticationPolicy creates a request policy factory that uses a "token cycler" and a SimpleBearerAuthorizationPolicy to manage applying bearer authorization to requests. The policy has no smarts other than asking the cycler for a token, applying it, and then applying the rest of the request policy chain.
  • The "token cycler" is a function that returns an AccessToken reliably. It is constructed from a TokenCredential and scopes.
    • It follows very simple rules:
      • If it MUST refresh (token is null, within one second of expiration, or expired), then it will await a refresh.
      • If it SHOULD refresh (token is within two minutes of expiration), then it will start a refresh.
      • Produces the cached token (which must be valid at this point).
    • It will not allow multiple refreshes to transpire at the same time, instead caching the Promise that represents the work of refreshing.
  • beginRefresh creates a refresh job that takes a function producing AccessToken | null and a refresh interval (in milliseconds, default of three seconds), and continually tries to retrieve an AccessToken until it is successful, waiting for the specified interval between attempts.

This doesn't change (add or remove) any API surface.

I've also merged the tests that @sadasant helpfully provided and repaired them to work with the new default values.

@witemple-msft witemple-msft force-pushed the core-http/new-refresher branch from adede61 to 443f7b3 Compare March 10, 2021 20:29
Copy link
Copy Markdown
Contributor

@sadasant sadasant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautifully done! Thank you 👏

@witemple-msft
Copy link
Copy Markdown
Member Author

Check enforcer is running into some trouble this morning with events making it back from AZDO, but we're green on the merge of 75f9a1b:

image

Copy link
Copy Markdown
Member

@jeremymeng jeremymeng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Would love to have @xirzec take a look too.

Copy link
Copy Markdown
Member

@xirzec xirzec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is clever!

Can we mark AccessTokenRefresher and AccessTokenCache as deprecated as part of this PR?

The only other real concern I have is the case where getToken fails forever and we stubbornly never raise an error the developer can handle. Feels like there should be some limit.

scopes,
timeBetweenRefreshAttemptsInMs
);
async function beginRefresh(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this never gives up? Is there any case it could get stuck forever? What happens if I pass in a token credential with a bad endpoint or something?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of any reason a TokenCredential would actually return null. It bothers me that the API is written in such a way that resolving with null is possible, but I think for compatibility reasons we are stuck with it. What I guess we should be doing here instead of checking for null is catching rejections.

We could just use a timer? If the refresh doesn't complete within five seconds after expiration (or within five seconds period if there is no token), we can just reject the refresher. Any requests queued up waiting for that refresh job will reject as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the logic proposed by @sadasant below. If the existing token has already timed out, we retry one more time and then surface an exception to all awaiters if it failed again.

@sadasant
Copy link
Copy Markdown
Contributor

@willmtemple @jeremymeng are we missing something here? just to make sure we're not missing anything.

@witemple-msft
Copy link
Copy Markdown
Member Author

@sadasant I need to get back to this. I'll update this later today. I am wondering whether/what to do about the refreshing forever issue. Timeout seems pretty simple and low risk, and I'm wondering whether I should catch exceptions as well.

@sadasant
Copy link
Copy Markdown
Contributor

@witemple-msft right when the token expires there should be at most one more refresh, and no more refreshes. After that we should assume we weren't able to refresh the token and let the network request throw naturally with the expired token.

@ghost
Copy link
Copy Markdown

ghost commented Mar 30, 2021

Hello @witemple-msft!

Because this pull request has the auto-merge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit 2af43ed into Azure:master Mar 30, 2021
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants