Skip to content
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

Support client capabilities and claims #355

Merged
merged 4 commits into from
Nov 15, 2022
Merged

Support client capabilities and claims #355

merged 4 commits into from
Nov 15, 2022

Conversation

chlowell
Copy link
Collaborator

Closes #263 by adding a WithClientCapabilities option for client constructors and a WithClaims option for token acquisition methods. Usage looks like this:

client, err := New("id", cred, WithClientCapabilities([]string{"CP1"}))
token, err := client.AcquireTokenByCredential(ctx, scope, WithClaims("some JSON"))

(confidential and public clients have the same options)

I believe I've covered all the bases here (see the e2e tests) but please look closely; many code paths are impacted. Let me highlight a few subtleties:

  • capabilities must interpolate into a valid JSON array but otherwise we have no opinion on their validity
  • claims must be valid JSON. Callers are responsible for decoding challenges. This is documented and the error returned for invalid JSON calls this out.
  • AcquireTokenSilent(..., WithClaims("...")) won't return a cached access token. This means it always fails for confidential clients. It can succeed for public clients that have a refresh token for the account. Similarly, AcquireTokenOnBehalfOf can succeed silently for confidential clients.

@chlowell chlowell added the enhancement New feature or request label Oct 13, 2022
@sonarcloud
Copy link

sonarcloud bot commented Oct 25, 2022

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 8 Code Smells

No Coverage information No Coverage information
0.5% 0.5% Duplication

Copy link
Contributor

@rayluo rayluo left a comment

Choose a reason for hiding this comment

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

Overall LGTM. Leaving a couple minor comments below.

apps/confidential/confidential.go Show resolved Hide resolved
apps/confidential/confidential_test.go Show resolved Hide resolved
Comment on lines +308 to +311
case "password":
mockClient.AppendResponse(mock.WithBody([]byte(`{"account_type":"Managed","cloud_audience_urn":".","cloud_instance_name":".","domain_name":"."}`)))
case "passwordFederated":
mockClient.AppendResponse(mock.WithBody([]byte(`{"account_type":"Federated","cloud_audience_urn":".","cloud_instance_name":".","domain_name":".","federation_protocol":".","federation_metadata_url":"."}`)))
Copy link
Contributor

Choose a reason for hiding this comment

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

claims: `{"access_token":{"nbf":{"essential":true, "value":"42"}}}`,
expected: `{"access_token":{"nbf":{"essential":true, "value":"42"}, "xms_cc":{"values":["cp1","cp2"]}}}`,
},
} {
Copy link
Member

Choose a reason for hiding this comment

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

That is a great unit test, very easy to read and understand.

func (cca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts ...AcquireSilentOption) (AuthResult, error) {
o := AcquireTokenSilentOptions{}
if err := options.ApplyOptions(&o, opts); err != nil {
return AuthResult{}, err
}

if o.claims != "" {

Choose a reason for hiding this comment

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

Is it WithClaims() that adds this in? If so, maybe we shouldn't mention it as one of the Options since it will lead immediately into an error.

Copy link
Collaborator Author

@chlowell chlowell Nov 10, 2022

Choose a reason for hiding this comment

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

You raise a good point. It will one day be possible for this method to succeed when given claims; we can add the option then. The value of taking the option today is in maintaining the calling pattern used in all other cases (see e.g. azidentity):

  1. call AcquireTokenSilent (check the cache)
  2. if that returned an error, call AcquireTokenBy* (request a new token)

Applications expect errors from AcquireTokenSilent because it commonly returns them, which is okay because that's just a cache miss. Having an option that guarantees it will return an error therefore doesn't make users write more, different, or tricky code. On the other hand, if this method doesn't take claims, the probably miniscule population of users who use this feature will have to write conditional code around it, then rewrite that code when this method does take claims. But sure, it's weird to have an anti-useful option.

Edit: whoops, I buried the lede there: having this option steers users away from an authorization failure loop. When a user presents an access token to some RP and receives a claims challenge in response, that means the token isn't valid, even if it hasn't yet expired; the user must acquire a new token having the requested claims. The MSAL client doesn't know this because it doesn't observe requests to the RP. So, a user who receives a claims challenge and proceeds to call AcquireTokenSilent will probably get an invalid token. This is what I was getting at above when I wrote that not having this option compels users to write conditional code: they would have to know not to call AcquireTokenSilent when handling a claims challenge.

Choose a reason for hiding this comment

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

I think I get what you mean - it's definitely not a straightforward case and it sounds like it's critical to consider that it's running as part of an overall pattern of calls, not just on its own (if I'm following your logic).

Resolving for now, explanation makes sense to me.

@chlowell chlowell requested review from richardpark-msft and jhendrixMSFT and removed request for richardpark-msft November 14, 2022 19:48
@chlowell chlowell merged commit 4abfea3 into dev Nov 15, 2022
@chlowell chlowell deleted the chlowell/capabilities branch November 15, 2022 17:10
@rayluo rayluo mentioned this pull request Jan 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature Request]Claims request parameter support in MSAL
5 participants