-
Notifications
You must be signed in to change notification settings - Fork 100
Refactored public surface area for device code flow #68
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
Conversation
Renamed PublicClientApplication constructor function and updated its sig to take an options struct. Updated AcquireTokenSilent() and AcquireTokenByDeviceCode() APIs to take a context.Context and options structs. Removed "Get" prefix from getter-type methods. Replaced some interface return types with their underlying type.
| var err error | ||
| if userAccount != nil { | ||
| // found a cached account, now see if an applicable token has been cached | ||
| // NOTE: this API conflates error states, i.e. err is non-nil if an applicable token isn't |
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.
Any suggestions on this?
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 been thinking about this one a bit. It's somewhat similar to calling an io.Reader returning io.EOF at the end of a stream.
I'm not familiar enough with how customers use this functionality. Does it seem valuable to return a distinct error, e.g. msal.ErrNoToken (something like that)? Are there use-cases where distinguishing this specific case is valuable?
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.
MSAL errors codes are mostly well known. e.g. it may be a MsalClientException, an MsalServiceException. And there are specific error codes that lets a developer write the code in a specific way. we should do the same here. Java, Python, Node and .NET show cases this.
However error handling in general is not taken care of across the library
In reply to: 515436829 [](ancestors = 515436829)
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 took a cursory look at the C# implementation and it doesn't appear to throw an exception if the account isn't found (it's not doc'ed that way, and I dug a bit into the implementation and didn't see anyplace where it obviously throws).
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.
GetAccounts doesn't throw. My comment was arround the API in general (sorry for not being clear about that). My main comment on errors was that we should take that as a separate work item (already tracked elsewhere). We should simply follow the std. msal patterns for user flows and that will lead to specific expected errors that guide the developer and the user experience.
| } | ||
|
|
||
| // AcquireTokenByDeviceCodeOptions contains the optional parameters used to acquire an access token using the device code flow. | ||
| type AcquireTokenByDeviceCodeOptions struct { |
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 use this in our track 2 SDKs to reduce the chance of breaking changes. It works well for generated code where there tends to be more churn. For hand-written APIs where we can be tactical it might not make as much sense. Any thoughts on this one?
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 think it's fine to have an option object for all flows, though it there is nothing today then likelihood of anything in the future is relatively small. I would though actually have expected some option
In reply to: 516285138 [](ancestors = 516285138)
| ) | ||
|
|
||
| // ConfidentialClientApplicationOptions configures the PublicClientApplication's behavior. | ||
| type ConfidentialClientApplicationOptions struct { |
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.
A few things I'd note here:
- We really need to do something about these names (though probably not in this PR)
- We should not use the options struct. This is non-go standard. I know the official SDK track 2 guidelines say to do this, but since we aren't in the SDK, we should take advantage of variardic options. This struct would become private and modified by those args. It handles better defaults.
- HTTPClient, which we are renaming somewhere above, should just be eliminated in the end. If we really have a use case, we can add it back later (but I doubt we will).
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.
The Go blog details both variadic config and optional args struct as cromulent ways for handling configuration. There's a nice write-up as part of the module compatibility blog post.
https://blog.golang.org/module-compatibility
We (the Go SDK team) went round and round on this, and in the end we felt that the self-documenting properties of the optional params struct is the better customer experience. Note that we haven't had any usability studies as of yet, so our position may change.
Having a configurable HTTP client allows the use of something like our pipeline middleware for controlling request/response lifetime. Most common case is the ability to specify a retry policy for handling transient failures. At any rate we can defer it for now.
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.
And to be clear, I'm not saying it has to be optional args struct, just providing my perspective (while I do think there's benefit of having consistency across SDKs, this isn't the hill I'm gonna die on).
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 have bring your own HttpClient in all our libraries. Has been a wish so many times that we are providing it out of the box with new libraries we write.
|
While there is more work to be done, I think think change makes the MSAL api more clear and is an improvement. |
|
Addresses #60 |
jmprieur
left a comment
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.
LGTM
I left a few comments and questions, mainly about the consistency with the other MSAL libraries
|
|
||
| // GetAccounts gets all the accounts in the token cache. | ||
| func (cca *ConfidentialClientApplication) GetAccounts() []AccountProvider { | ||
| // Accounts gets all the accounts in the token cache. |
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 it make sense to returns the accounts in a confidential client application given that there will be one cache per account, and therefore Accounts will always be empty?
We have a GitHub issue to remove it from the ConfidentialClientApplication public API in MSAL.NET. See AzureAD/microsoft-authentication-library-for-dotnet#2141
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.
Opened #73 to track this.
| authCodeParams.Code = code | ||
| authCodeParams.CodeChallenge = config.CodeChallenge | ||
| result, err := publicClientApp.AcquireTokenByAuthCode(authCodeParams) | ||
| result, err := publicClientApp.AcquireTokenByAuthCode(context.Background(), config.Scopes, config.RedirectURI, &msal.AcquireTokenByAuthCodeOptions{ |
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.
For Auth Code flow there is a need for a reply URI, but it should be https://login.microsoftonline.com/common/oauth2/nativeclient. Do we need to pass it as a parameter?
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.
Is it always the same URL? What about for Azure Stack and/or sovereign clouds?
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.
yes, it's always the same URL. Just there is a need for one, but MSAL stops the redirect (to that URLà, and grabs the auth code on the fly, so it's never reached.
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 see. So just to confirm, we always specify this same redirect URL but since we never follow it there's no need to make it configurable, is that correct?
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.
The redirectURI param has been removed.
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.
@jmprieur I've removed the param, anything else or is this good to merge?
| result, err := publicClientApp.AcquireTokenSilent(silentParams) | ||
| var result *msalbase.AuthenticationResult | ||
| var err error | ||
| if userAccount != nil { |
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.
Do we need to test if the userAccount is not nil? couldn't just AcquireTokenSilent return an error if the userAccount is nil? (Other MSALs do that)
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.
With the current implementation, a non-nil error is ambiguous; it means that either the user account wasn't found or that something else failed (e.g. perhaps there was a transient IO failure making the network request).
I think we need to decide on the semantics we want here, and also look at what kind of error(s) other implementations expose here. I've opened #70 to track standardizing on error types.
keegan-caruso
left a comment
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.
LGTM. Definitely a step in the right direction.
|
SonarCloud Quality Gate failed.
|
|
Fixes #56 |
jennyf19
left a comment
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.
![]()
Renamed PublicClientApplication constructor function and updated its sig
to take an options struct.
Updated AcquireTokenSilent() and AcquireTokenByDeviceCode() APIs to take
a context.Context and options structs.
Removed "Get" prefix from getter-type methods.
Replaced some interface return types with their underlying type.