Skip to content

Conversation

@jhendrixMSFT
Copy link
Contributor

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.

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
Copy link
Contributor

Choose a reason for hiding this comment

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

Any suggestions on this?

Copy link
Contributor Author

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?

Copy link
Contributor

@henrik-me henrik-me Oct 31, 2020

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)

Copy link
Contributor Author

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).

Copy link
Contributor

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 {
Copy link
Contributor Author

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?

Copy link
Contributor

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 {
Copy link
Contributor

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).

Copy link
Contributor Author

@jhendrixMSFT jhendrixMSFT Nov 2, 2020

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.

Copy link
Contributor Author

@jhendrixMSFT jhendrixMSFT Nov 2, 2020

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).

Copy link
Contributor

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.

@henrik-me
Copy link
Contributor

While there is more work to be done, I think think change makes the MSAL api more clear and is an improvement.

@jhendrixMSFT jhendrixMSFT marked this pull request as ready for review November 3, 2020 00:21
@jhendrixMSFT
Copy link
Contributor Author

Addresses #60

Copy link

@jmprieur jmprieur left a 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.
Copy link

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

Copy link
Contributor Author

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{
Copy link

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?

Copy link
Contributor Author

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?

Copy link

@jmprieur jmprieur Nov 4, 2020

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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 {
Copy link

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)

Copy link
Contributor Author

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.

Copy link
Contributor

@keegan-caruso keegan-caruso left a 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.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Nov 5, 2020

SonarCloud Quality Gate failed.

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities (and Security Hotspot 0 Security Hotspots to review)
Code Smell A 0 Code Smells

No Coverage information No Coverage information
4.0% 4.0% Duplication

@jhendrixMSFT
Copy link
Contributor Author

Fixes #56

Copy link
Contributor

@jennyf19 jennyf19 left a comment

Choose a reason for hiding this comment

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

:shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants