Skip to content

support of STS throttling instructions + caching of InteractionRequiredException… #201

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

Merged
merged 5 commits into from
May 5, 2020

Conversation

SomkaPe
Copy link
Contributor

@SomkaPe SomkaPe commented Mar 28, 2020

  • support of STS throttling instructions - 429 Retry-After header + 429, 5xx response status codes
  • caching of InteractionRequiredException responses for refresh token grant requests
  • refactoring

issue #176

Copy link
Contributor

@sangonzal sangonzal 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. Just a couple of questions.

@@ -30,25 +30,26 @@ static MsalServiceException fromHttpResponse(HTTPResponse httpResponse){
errorResponse.statusCode(httpResponse.getStatusCode());
errorResponse.statusMessage(httpResponse.getStatusMessage());

if(errorResponse.error() != null &&
if (isRefreshTokenGrantRequest &&
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if it makes sense to just limit throwing InteractionRequiredError for just RefreshTokenGrant. What about in the case of username password and integrated windows authentication? interaction required is just as relevant in those cases as it is for refresh flow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

both cases are "interactive", if it fails It means that provided credentials are wrong, there is no plan B - plan B exists only for silent flow

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I would consider IWA and U/P interactive. To me it seems that an interactive request is one where the IDP renders UI, which is not the case for either of those.

Also IWA and U/P can fail for more than just providing wrong credentials - one example is MFA. In this case, someone doing U/P could theoretically fall back to doing an interactive flow to meet the MFA requirement. I agree that it probably does not make sense to do this in an application, but anyways the InteractionRequired error is still 100% valid and the most transparent error to expose to an user. In the case that we made the change you are recommending, and someone is using one of this flows with MFA enabled on the tenant, what kind of error would they see?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

interactive vs non-interactive/silent distinguishing does make sense only for flows where user perform authorization "interactively" - like auth code flow - user redirected to identity provider authorization endpoint and he interact with it - so identity provider can give him additional challenges and he can satisfy it. In case of U/P or WIA - if credentials are wrong or missing there is not much we can do, we do not have plan B (different api) to handle it, like we do with auth code flow

Copy link
Member

@bgavrilMS bgavrilMS Apr 10, 2020

Choose a reason for hiding this comment

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

This is a great point @sangonzal,
I have seen many client applications implementing flow as:

  • try AcquireTokenSilent
  • catch -- try IWA
  • catch -- use Interactive

In fact, the spec states

`... for silent flow some errors of WS-Trust flow should go in UI required cache as well as errors from token endpoint. Applications have tendency to loop on WS-Trust flow, which would result in DDoS of UserRealm endpoint. There is no way such cases for the application to proceed without showing UI. Here is the list of these errors:

account is managed/unknown,
integration is not WS-Trust (SAML2.0)
missing MEX endpoint,
invalid MEX document,
MEX doesn’t have Windows Transport Endpoint,
failed to get SAML token from ADFS,
invalid_grant,
interaction_required.`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

throttling design also explicitly highlight that scope of responses caching is silent requests.

Copy link
Contributor

Choose a reason for hiding this comment

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

"it is about silent mode, only one type of requests can be sent in silent mode - refresh token grant" I don't agree with you here. The most meaningful difference IMO between "silent mode" and "non-silent mode" is showing UI. From that perspective the other flows are silent as well.

InteractionRequired is a valid error to expose for the other flows. What would we expose to users in the case of MFA for example (which is relevant in all flows)? InteractionRequired (pulling up UI and having someone click on it) is the way to resolve that issue, and the most transparent exception to expose to users, whether it's actionable or not for them.

From the perspective of throttling, wouldn't we also want to throttle applications stuck in a loop in ROPC or IWA flows if they are getting interaction required returned from the server?

Copy link
Contributor

@sangonzal sangonzal Apr 13, 2020

Choose a reason for hiding this comment

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

One more thing to consider is that changing this behavior (only throwing InteractionRequired for refresh token grant) is a breaking change, and would require bumping up the major version.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"InteractionRequired is a valid error to expose for the other flows." - even for non interactive flows ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removing redefinition of InteractionRequired Exception from the scope of this PR

@henrik-me henrik-me requested review from bgavrilMS and rayluo April 7, 2020 21:35
@henrik-me
Copy link
Contributor

@bgavrilMS @rayluo : please add comments and discuss offline as needed.

return StringHelper.createSha256Hash(sb.toString());
}

static private boolean isRetriable(IHttpResponse httpResponse) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: typo retryable

Copy link
Contributor Author

@SomkaPe SomkaPe Apr 29, 2020

Choose a reason for hiding this comment

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

ok, will rename it, was not sure what is correct spelling

long retryInMs = ThrottlingCache.retryInMs(requestThumbprint);

if (retryInMs > 0) {
throw new MsalThrottlingException(retryInMs);
Copy link
Member

Choose a reason for hiding this comment

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

I was under the impression that throttling concept is not exposed to client. We should just throw the old exception, as if the client had made the HTTP call and the server returned the same error.

I think this makes sense because it would behave well with retry policies that customers have added on their own.

Copy link
Contributor Author

@SomkaPe SomkaPe Apr 10, 2020

Choose a reason for hiding this comment

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

Not sure about it, if we get instructions from STS to throttle some requests, we do it and do it transparently for app developers.

Copy link
Member

Choose a reason for hiding this comment

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

I sent an email to Sasha for clarifications as I am also not sure about this.

*/
@Accessors(fluent = true)
@Getter
public class MsalThrottlingException extends MsalServiceException {
Copy link
Member

Choose a reason for hiding this comment

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

As stated above, ideally we wouldn't expose a new type of exception, but just rethrow an old MsalServiceExcpetion.

Copy link
Contributor Author

@SomkaPe SomkaPe Apr 10, 2020

Choose a reason for hiding this comment

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

not sure that covering up what really happened is a good idea

Copy link
Member

@bgavrilMS bgavrilMS left a comment

Choose a reason for hiding this comment

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

Generally looks good, clean implementation. Things to discuss:

  • not exposing throttling concept to end users (i.e. no new exception)
  • UI required exception caching should cover IWA and ROPC as well

headers.putAll(httpResponse.headers());

if (headers.containsKey(RETRY_AFTER_HEADER) && headers.get(RETRY_AFTER_HEADER).size() == 1) {
int headerValue = Integer.parseInt(headers.get(RETRY_AFTER_HEADER).get(0));
Copy link
Member

Choose a reason for hiding this comment

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

I was looking at the spec for Retry-After and it looks like it can also be a timestamp https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After

Consider using a library call that can parse 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.

good point, but according to design doc we server side are going to use only delta-seconds format


RefreshTokenGrant refreshTokenGrant = new RefreshTokenGrant(new RefreshToken(parameters.refreshToken()));
return new OAuthAuthorizationGrant(refreshTokenGrant, parameters.scopes());
}

private String getRefreshTokenRequestFullThumbprint() {
StringBuilder sb = new StringBuilder();
Copy link
Member

Choose a reason for hiding this comment

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

As per spec, this should include all possible inputs except the refresh token. Might be better to do it based on the form body params...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

under request parameters I mean set of parameters passed to corresponding api - silent api or refresh token grant api


IApiParameters apiParameters = requestContext.apiParameters();

if (apiParameters instanceof SilentParameters) {
Copy link
Member

Choose a reason for hiding this comment

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

AcquireTokenSilent exists both on Public and Confidential clients, but we should only throttle Public client as per spec. Not sure if it's ok to throttle Confiential clients so aggressively. Maybe it is ...

I don't have all the context on how MSAL.java is organized, so maybe this comment isn't relevant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

check for public client happens in checkForThrottling and processThrottlingInstructions functions

}

@Test
public void STSResponseContainsStatusCode500_repeatedRequestsThrottled() throws Exception {
Copy link
Member

Choose a reason for hiding this comment

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

Sasha wrote 21 acceptance tests. I was thinking it is too much, but there are subtle differences in each one. It's also a guarantee that libraries behave the same.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please ensure the relevant acceptance tests are included here as that is the best way we have to ensure the code behaves as expected.


In reply to: 412276923 [](ancestors = 412276923)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Most of mentioned tests duplicated, and some not applicable for java.
Taking to account differences in api model of Msal and WAM we consider only refresh tokens grant requests as subject to UIRequired cache

}

static private boolean isRetriable(IHttpResponse httpResponse) {
return httpResponse.statusCode() >= 500 && httpResponse.statusCode() < 600;
Copy link
Member

@bgavrilMS bgavrilMS Apr 21, 2020

Choose a reason for hiding this comment

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

I think we should also not retry if a Retry-After header is present. I just realised this implementing some of Sasha's tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point - going to change it.

Copy link
Member

@bgavrilMS bgavrilMS 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, I think we are on the same page after offline discussion

@SomkaPe SomkaPe merged commit 8a79bc8 into dev May 5, 2020
SomkaPe added a commit that referenced this pull request May 5, 2020
…edException… (#201)

support of server side throttling instructions + caching of InteractionRequiredException responses
@siddhijain siddhijain deleted the pesomka/throttling branch January 19, 2022 18:27
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.

4 participants