-
Notifications
You must be signed in to change notification settings - Fork 33
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
Make token credentials compatible with @azure/core-auth's TokenCredential #66
Conversation
Worth noting that the CI build will fail because we haven't shipped |
80f4186
to
c9f02fc
Compare
PR Azure/azure-sdk-for-js#4367 restricts the |
lib/credentials/coreAuthHelpers.ts
Outdated
export function prepareToken<T extends TokenResponseLike>( | ||
token: T, | ||
scopes: string | string[] | undefined): T | AccessToken { | ||
if (scopes) { |
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.
What if scopes
is an empty string ? This check will fail for that. If the intention is to check specifically for null
or undefined
then using the "double equal to" or "not with a single equal to" will be a good check as that will take care of null
and undefined
.
if (scopes != undefined)
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.
Thanks! It shouldn't be an empty string, but you never know what a user might do. I'll make the check stricter.
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.
On second thought, the idea is check for whether a parameter was passed to scopes
, not to scrutinize whether the value of scopes
is valid. I think in this case I'm only concerned about whether it's explicitly undefined
since that's what the parameter would contain if nothing was passed.
BearerTokenAuthenticationPolicy
in core-http
will always pass a string or an array to the scopes
parameter so I think it's pretty safe to only check for undefined
since we don't actually use the real value of scopes
in these forward-compatible implementations anyway.
package.json
Outdated
@@ -28,6 +28,7 @@ | |||
"tsconfig.json" | |||
], | |||
"dependencies": { | |||
"@azure/core-auth": "1.0.0-preview.1", | |||
"@azure/ms-rest-azure-env": "^1.1.2", | |||
"@azure/ms-rest-js": "^1.8.7", |
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.
Should this be replaced by @azure/core-http
? By not moving to core-http do we risk having type conflicts for the customer in his/her application?
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.
Nah, we pulled the TokenCredential
interface and related types into @azure/core-auth
so that they can be reused in other places. @azure/core-http
depends on @azure/core-auth
in the unshipped code in master
.
For safety, I would let this be a major version bump (3.0.0). It keeps a clear boundary between new stuff and the old stuff. Makes things easier for us to maintain. |
return { | ||
token: token.accessToken, | ||
expiresOnTimestamp: | ||
typeof token.expiresOn === "string" |
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.
This assumes that expiresOn
exists on all the credential types in ms-rest-nodeauth.
Are we sure about this assumption?
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.
Good point, I can't find expiresOn
being assigned anywhere in a few of the credential implementations. @amarzavery, do you remember how users are supposed to deal with token expiration in this library? I only see AzureCliCredentials
implementing any kind of token refresh logic.
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.
adal-node has an in memory token cache. It gets a new token if it is about to get expired or gives the token from the token cache.
For AzureCliCredentials we are calling the cli to authenticate and give us the new token that will be valid for the next one hour. Cli knows how to refresh the token if it is using it internally. We can't be calling the Cli for every request, as that will terribly slow down the sdk (spawning a new process and asking the Cli for creds).
Thus we try to do the same thing that adal-python would do anyways (if we are within the token refresh window (last 5 mins before the token expiry) then ask the Cli for token, it will ask adal-python which will then get the new token that will be valid for the next one hour).
Thus we need to check the expiresOn property.
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.
I've loosened the expectation of an expiresOn
property on the token and put a default expiration timestamp of an hour if one is not provided. What do you think?
lib/credentials/coreAuthHelpers.ts
Outdated
@@ -0,0 +1,28 @@ | |||
import { AccessToken } from "@azure/core-auth"; | |||
|
|||
export interface TokenResponseLike { |
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.
Does this have to be exported? I don't see it being referenced anywhere other than this file
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.
Possibly not, I'll give it a shot.
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.
Looks like removing the export worked fine!
@@ -74,7 +78,7 @@ export class ApplicationTokenCertificateCredentials extends ApplicationTokenCred | |||
if (tokenResponse.error || tokenResponse.errorDescription) { | |||
return reject(tokenResponse); | |||
} | |||
return resolve(tokenResponse as TokenResponse); | |||
return resolve(prepareToken(tokenResponse as TokenResponse, scopes)); |
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.
Nit: Where-ever applicable, please consider having a const for the prepared token instead of inlining the call to prepareToken
in this manner. Having the call in a separate line would make it easier to add breakpoints when debugging
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.
Sounds good, I'll make that change.
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.
Done!
import { assert } from "chai"; | ||
import { prepareToken } from "../../lib/credentials/coreAuthHelpers"; | ||
|
||
describe("prepareToken", function() { |
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.
There are 2 test files for MSI creds at the moment.
Can we update the test cases in these files to include a call to getToken(scopes) to check that the returned value is of type AccessToken
?
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.
I'll give it a shot
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.
Added a test in each MSI spec file to verify that an AccessToken
is returned when a scope is passed to getToken
. Is that sufficient?
Having creds from ms-rest-nodeauth be compatible with core-auth doesnt solve the problem that was being fixed in Azure/azure-sdk-for-js#4335 does it? Or are we expecting that a combination of the above and Azure/azure-sdk-for-js#4367, solves the issue in Azure/azure-sdk-for-js#4335 |
Yep, it's the combination of fixes that mostly solves the problem. Previously the problem was that After these new two PRs, we only ever use This does leave us with one problem, though: we don't have a reliable way to distinguish between a I'd be happy to hear if someone has an idea for how to get around this problem! |
Had a better idea on how to make |
@@ -120,7 +122,9 @@ export class AzureCliCredentials implements TokenClientCredentials { | |||
* changed else uses the cached accessToken. | |||
* @return The tokenResponse (tokenType and accessToken are the two important properties). | |||
*/ | |||
public async getToken(): Promise<TokenResponse> { | |||
public getToken(): Promise<TokenResponse>; | |||
public getToken(scopes: string | string[], options?: GetTokenOptions): Promise<AccessToken>; |
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.
Why no async
keywords for the overloads?
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.
Because the overload definitions are just typings and async
is only really needed with a function body that uses await
. That said, I'll add the keywords to minimize confusion.
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.
My confusion was because in a couple of places we did add the async
keyword for the overloads, and in some places we didnt
public getToken(): Promise<TokenResponse> { | ||
public getToken(): Promise<TokenResponse>; | ||
public getToken(scopes: string | string[], options?: GetTokenOptions): Promise<AccessToken>; | ||
public async getToken(scopes?: string | string[]): Promise<TokenResponse | AccessToken> { |
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.
Missing async keywords on the overloads
Changelog.md
Outdated
@@ -1,4 +1,9 @@ | |||
# Changelog | |||
|
|||
## 3.0.0 - 2019/07/19 |
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.
Date would need an update
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.
I'll update it before merging
565a9fe
to
10e63d5
Compare
Merge from master and update deps
This PR is no longer necessary as we've made |
This change makes
ms-rest-nodeauth
token credential implementations compatible with@azure/core-auth
'sTokenCredential
, enabling them to be used with Track 2 SDK libraries. This is part of the resolution for Azure/azure-sdk-for-js#4108.I've verified these changes with
DeviceTokenCredentials
,AzureCliCredentials
, andApplicationTokenCredentials
using a version of@azure/arm-resources
that does not acceptServiceClientCredentials
in itsServiceClient
constructor. I also removed the use ofsigningPolicy
in@azure/core-auth
in my local build just to make sure these credentials are being used withBearerTokenAuthenticationPolicy
.One implication of this change is that the fix we made to
isTokenCredential
in Azure/azure-sdk-for-js#4335 now has to be rolled back because all of these credentials now expose bothgetToken
andsignRequest
due to implementing bothServiceClientCredentials
andTokenCredential
. I'll make a PR to@azure/core-http
if we decide we like this compatibility approach.Questions for Reviewers
prepareToken
? Doesn't seem explicit enough to me so I'm open to ideas for a better name.