okta / okta-oidc-android

OIDC SDK for Android
https://github.com/okta/okta-oidc-android
Other
60 stars 45 forks source link

sessionClient.refreshToken returning generic 400 bad request error #332

Closed ssawchenko closed 1 year ago

ssawchenko commented 1 year ago

Describe the bug?

sessionClient.refreshToken is returning a generic 400 bad request error for some or our users. I have been unable to reproduce this locally, but in our crash reporting we see the following error:

AuthClient.awaitRefreshTokens.onError called 
  with exception: AuthorizationException: {"type":0,"code":0,"errorDescription":"Invalid status code 400 Bad Request"}
    at com.okta.oidc.net.request.TokenRequest.executeRequest(TokenRequest.java:23)
    at com.okta.oidc.clients.sessions.SyncSessionClientImpl.refreshToken(SyncSessionClientImpl.java:5)
    at com.okta.oidc.clients.sessions.SessionClientImpl.lambda$refreshToken$15(SessionClientImpl.java:2)
    at com.okta.oidc.clients.sessions.SessionClientImpl.a(Unknown Source:0)
    at com.okta.oidc.clients.sessions.d.run(Unknown Source:2)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at java.lang.Thread.run(Thread.java:920)
Caused by: com.okta.oidc.net.HttpStatusCodeException: Invalid status code 400 Bad Request
    at com.okta.oidc.net.HttpResponse.asJson(HttpResponse.java:10)
    at com.okta.oidc.net.request.TokenRequest.executeRequest(TokenRequest.java:3)

My guess is that the refresh tokens have expired for these users, however, the error does not indicate that this is the problem.

I see that there have been reports in the past for ambiguous error codes (#274) and I am wondering if this is somewhat related.

What is expected to happen?

If this was in fact a valid refresh token expiry, according to this ticket (#274) I would expect an error along the lines of:

AuthorizationException: {"type":2,"code":0,"errorDescription":"Expired refresh token"}

What is the actual behavior?

I am being given the error:

AuthorizationException: {"type":0,"code":0,"errorDescription":"Invalid status code 400 Bad Request"}

which is not very descriptive about what the actual problem is.

Reproduction Steps?

Unable to reproduce this locally - I am guessing I have to wait the refresh token expiry on a device. I have a device sitting right now, but there is still awhile on the expiry before I can test this out.

But we have a bunch of Sentry reports that indicate the same error is being returned to numerous users and that this is not simply a one off case.

Additional Information?

People have been commenting in https://github.com/okta/okta-oidc-android/issues/165 with similar issues, but since that ticket is closed I am unsure it is getting visibility.

SDK Version

1.3.2

Build Information

No response

JayNewstrom commented 1 year ago

The response from the server when a refreshToken is invalid is

{
    "error": "invalid_grant",
    "error_description": "The refresh token is invalid or expired."
}

This will result in throwing this error: https://github.com/okta/okta-oidc-android/blob/master/library/src/main/java/com/okta/oidc/net/request/TokenRequest.java#L135

Which should give you the information you're requesting. That being said, 400s could occur outside of that as well.

If you have more information, or ideally could reproduce this, we can get it fixed though!

ssawchenko commented 1 year ago

@JayNewstrom Thank you for your reply :)

I've never seen any reports with the message "The refresh token is invalid or expired", just numerous instances of "Invalid status code 400 Bad Request". Unfortunately these are coming from our Sentry reporting tool, I have yet to be able to repo this locally. I don't have a user that has an expired refresh token that I can test this on, as we do not have control over the actual Okta tenant for security reasons.

So with that in mind, I have some follow up questions:

JayNewstrom commented 1 year ago

We throw that exception in a few places: notably: https://github.com/okta/okta-oidc-android/blob/69a87fc000c721feee589f04d741cc137263afba/library/src/main/java/com/okta/oidc/net/HttpResponse.java#L109-L127

This could happen due to introspect/configuration/authorize calls.

The way I forced an invalid token is via a Charles Proxy breakpoint.

It seems like your line numbers aren't matching up in your stack trace. Are you using proguard/minification? This SDK isn't meant to be used with proguard, so you'll need to add a keep rule (which should already be happening automatically https://github.com/okta/okta-oidc-android/blob/master/library/proguard-rules.pro#L1)

ssawchenko commented 1 year ago

@JayNewstrom

It seems like your line numbers aren't matching up in your stack trace. Are you using proguard/minification?

The project that consumes my library does. I've gone back and added in the proguard rule to my library's consumer proguard file for the future just to be sure.

The way I forced an invalid token is via a Charles Proxy breakpoint.

I've gone through and tested this out in Charles, and when I invalidate the refresh token I do see the "invalid_grant" response, so it looks like the refresh token response is behaving correct when the refresh token is invalid.

So then if this issue is unrelated to refresh tokens being invalid, I'm at a loss for the actual cause. Generally 400 errors occur due to incorrectly typed URL, malformed syntax, or a URL that contains illegal characters, but in this case my requests are going fully through the oidc library call refreshToken and my app has little to no control over how it is being formatted. There was another person having an issue similar to mine (https://github.com/okta/okta-oidc-android/issues/165#issuecomment-827642953) that indicated it was occurring when their access token was invalid. Do you know if there is a way via Charles to invalidate an access token?

JayNewstrom commented 1 year ago

Access tokens aren't used as part of the refresh token process.

Another thing you could do is look through syslog for why the requests are failing.

ssawchenko commented 1 year ago

@JayNewstrom Ok I was able to reproduce this, and I again think it is related to the refresh tokens being invalid.

I ran the request in Charles again, invalidated the refresh token and in Charles I see the correct error being returned from the raw Okta call https://[clientname].okta.com/oauth2/v1/token: Screen Shot 2022-08-08 at 4 39 36 PM

But looking at my console log for my app while this was call was executed shows that the oidc API call refreshToken is actually returning me (the app) an Invalid status code 400 Bad Request error instead: Screen Shot 2022-08-08 at 4 39 25 PM

I can repo this each time this way.

JayNewstrom commented 1 year ago

The exception is being wrapped. You can access the inner exception via e.getCause()

ssawchenko commented 1 year ago

@JayNewstrom Cause looks to also return me the HttpStatusCodeException. Is there somewhere else the invalid grant error message could be found? image

JayNewstrom commented 1 year ago

It doesn't look like it's exposed. However, if you want to migrate to the new SDK, it's exposed as HttpResponseException.

ssawchenko commented 1 year ago

I was unaware of a new SDK, I'll open a ticket on our side and look to start migration in the future.

For now though, my main concern is making sure that the Sentry reports we are seeing are not actual issues (I wouldn't call an expired refresh token a problem). So from the above conversation, is the expected behaviour of refreshToken API call to return an "Invalid status code 400 Bad Request" response when given an invalid or expired refresh token? Because this is what I am still seeing locally.

JayNewstrom commented 1 year ago

Yes, this is an expected error.

ssawchenko commented 1 year ago

@JayNewstrom Excellent, I'll update my docs and Sentry reporting to note this, and we'll look to migrate to the new SDK in the future.

Thank you for your time!

JayNewstrom commented 1 year ago

Please let me know if you have any feedback on the new SDK too! Thanks

JayNewstrom commented 1 year ago

I'm going to close this for now. But if you have more questions, feel free to open a new issue!