-
Notifications
You must be signed in to change notification settings - Fork 148
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
Conversation
…edException responses
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 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 && |
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 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.
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.
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
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.
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?
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.
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
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 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.`
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.
throttling design also explicitly highlight that scope of responses caching is silent requests.
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.
"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?
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.
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.
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.
"InteractionRequired is a valid error to expose for the other flows." - even for non interactive flows ?
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.
removing redefinition of InteractionRequired Exception from the scope of this PR
src/main/java/com/microsoft/aad/msal4j/RefreshTokenRequest.java
Outdated
Show resolved
Hide resolved
@bgavrilMS @rayluo : please add comments and discuss offline as needed. |
return StringHelper.createSha256Hash(sb.toString()); | ||
} | ||
|
||
static private boolean isRetriable(IHttpResponse httpResponse) { |
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: typo retryable
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.
ok, will rename it, was not sure what is correct spelling
long retryInMs = ThrottlingCache.retryInMs(requestThumbprint); | ||
|
||
if (retryInMs > 0) { | ||
throw new MsalThrottlingException(retryInMs); |
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 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.
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.
Not sure about it, if we get instructions from STS to throttle some requests, we do it and do it transparently for app developers.
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 sent an email to Sasha for clarifications as I am also not sure about this.
*/ | ||
@Accessors(fluent = true) | ||
@Getter | ||
public class MsalThrottlingException extends MsalServiceException { |
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.
As stated above, ideally we wouldn't expose a new type of exception, but just rethrow an old MsalServiceExcpetion.
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.
not sure that covering up what really happened is a good idea
src/test/java/com/microsoft/aad/msal4j/UIRequiredCacheTest.java
Outdated
Show resolved
Hide resolved
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.
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)); |
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 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.
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, 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(); |
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.
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...
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.
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) { |
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.
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.
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.
check for public client happens in checkForThrottling and processThrottlingInstructions functions
} | ||
|
||
@Test | ||
public void STSResponseContainsStatusCode500_repeatedRequestsThrottled() throws Exception { |
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.
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.
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.
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)
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.
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; |
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 we should also not retry if a Retry-After header is present. I just realised this implementing some of Sasha's tests.
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 - going to change it.
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 good, I think we are on the same page after offline discussion
…edException… (#201) support of server side throttling instructions + caching of InteractionRequiredException responses
issue #176