okta / okta-oidc-android

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

Okta E0000003 (The request body was not well-formed.) #259

Closed johnnyzen closed 2 years ago

johnnyzen commented 2 years ago

Hi all, started getting this error when logging in, without any changes to my code.

Okta E0000003 (The request body was not well-formed.)

//Okta implementation 'com.okta.android:oidc-androidx:1.0.16' implementation 'com.okta.jwt:okta-jwt-verifier-impl:0.5.0' implementation 'com.okta.authn.sdk:okta-authn-sdk-api:1.0.0' implementation('com.okta.authn.sdk:okta-authn-sdk-impl:1.0.0') { exclude group: 'com.okta.sdk', module: 'okta-sdk-httpclient' } implementation 'com.okta.sdk:okta-sdk-okhttp:1.5.2'

Android 11 - Samsung s10

Any ideas on why this is happening, or any logs I can look at?

arvindkrishnakumar-okta commented 2 years ago

Thanks for posting! I'll forward this to our Android SDK team for addressal.

JayNewstrom commented 2 years ago

Hi @johnnyzen Could you please give us a code snippet of what's triggering this error?

JayNewstrom commented 2 years ago

Another area you could help is by seeing if the request is getting to Okta by checking the system log. You will have to change the URL, but it should be something along the lines of https://dev-change_me-admin.okta.com/report/system_log_2 for your org.

johnnyzen commented 2 years ago

Update: After going through hell, it appears Android Studio + Profiling is causing this issue.

I wasnt even using profiling, so maybe Android Studio is also doing some kind of profiling without user action in some situations.

Somehow it seems Android Studio is interfering with the request, but cannot find out what it is doing exactly.

Android Studio 4.2.1

Steps to reproduce.

1) Run application 2) Turn on profiling 3) Try to login using okta 4) Fails to login until I close profiling and relaunch app. - then it works.

I guess this is something I will investigate further with Android Studio, so I think this can be closed.

Although would be interesting if anyone can reproduce.

Hope this helps anyone experiencing the same issue.

JayNewstrom commented 2 years ago

Thanks for the update! I know part of Android Studio profiling includes intercepting/logging okhttp requests. If you want to use it, you might need to update the version of OkHttp you're using.

I'll close this for now, but feel free to reopen, or open a new issue if you run into problems.

johnnyzen commented 2 years ago

I know you guys are quite strict with the json.

This is the request I can see in the profiler when its failing. Maybe Android Studio is adding null to the relay state? Could this cause the request to fail on your side?

{
  "relayState": null,
  "password": "xxxxxx",
  "username": "xxxxxx"
}
johnnyzen commented 2 years ago

Raised with Android Studio bug tracker: https://issuetracker.google.com/issues/195441726

JayNewstrom commented 2 years ago

Could you give me a code snippet of how you're triggering the authentication?

My guess is that you're using https://github.com/okta/okta-auth-java not okta-oidc-android. (I'm happy to help you here either way, but it'll just make it easier for me to help you!)

johnnyzen commented 2 years ago
fun oktaNetworkLogin(loginRequest: OktaLoginRequest): LiveData<Resource<AuthenticationResponse>> {
        val response: MutableLiveData<Resource<AuthenticationResponse>> = MutableLiveData()
        appExecutors.diskIO().execute {
            val client = AuthenticationClients.builder()
                .setOrgUrl("https://apps.****.com")
                .build()
            try {
                client.authenticate(
                    loginRequest.username,
                    loginRequest.password.toCharArray(),
                    null,
                    object : AuthenticationStateHandlerAdapter() {
                        override fun handleUnknown(authenticationResponse: AuthenticationResponse) {
                            Timber.d("handleUnknown ${authenticationResponse.status.name}")
                            response.postValue(
                                Resource(
                                    Status.ERROR,
                                    authenticationResponse,
                                    "Server error. Please contact IT support."
                                )
                            )
                        }

                        override fun handleLockedOut(lockedOut: AuthenticationResponse) {
                            Timber.d("handleLockedOut ${lockedOut.status.name}")
                            response.postValue(
                                Resource(
                                    Status.ERROR,
                                    lockedOut,
                                    "You are locked out from your account. Please contact IT support."
                                )
                            )
                        }

                        override fun handleSuccess(successResponse: AuthenticationResponse) {
                            val sessionToken = successResponse.sessionToken
                            Timber.d("successResponse: Session Token: $sessionToken")
                            response.postValue(Resource(Status.SUCCESS, successResponse, null))
                        }
                    })
            } catch (e: Exception) {
                Timber.e("AuthenticationException $e")
                response.postValue(
                    Resource(
                        Status.ERROR,
                        null,
                        "Authentication failed. Please check details and try again.",
                        ErrorResponse(0)
                    )
                )
            }
        }

        return response
    }
johnnyzen commented 2 years ago

    implementation 'com.okta.android:oidc-androidx:1.0.16'
    implementation 'com.okta.jwt:okta-jwt-verifier-impl:0.5.0'
    implementation 'com.okta.authn.sdk:okta-authn-sdk-api:1.0.0'
    implementation('com.okta.authn.sdk:okta-authn-sdk-impl:1.0.0') {
        exclude group: 'com.okta.sdk', module: 'okta-sdk-httpclient'
    }
    implementation 'com.okta.sdk:okta-sdk-okhttp:1.5.2'
JayNewstrom commented 2 years ago

In our custom sign in example, https://github.com/okta/samples-android/tree/master/custom-sign-in

We're using:

    implementation 'com.okta.authn.sdk:okta-authn-sdk-api:2.0.0'
    runtimeOnly 'com.okta.authn.sdk:okta-authn-sdk-impl:2.0.0'
    runtimeOnly 'com.okta.sdk:okta-sdk-okhttp:2.0.0'
    runtimeOnly 'com.squareup.okhttp3:okhttp:4.9.0'

My guess is if you pull in the latest okta-sdk-okhttp and a newer okhttp, it'll work!

johnnyzen commented 2 years ago

Thanks @JayNewstrom I have applied the latest and greatest but still getting the same error.

I am pretty sure the problem lies with something Android Studio is doing. Maybe reconstructing the request and invalidating what you guys require.

JayNewstrom commented 2 years ago

Ok, if you can reproduce in one of our samples, I'd be happy to take a look.

johnnyzen commented 2 years ago

Sure I will give it a go :) thanks for your time and assistance @JayNewstrom

johnnyzen commented 2 years ago

@JayNewstrom Managed to recreate using your sample code. cloned your custom sign in code.

Changes I made to the sample project was literally okta_oidc_config details and changed base url.

JayNewstrom commented 2 years ago

So, I was able to recreate this, and it looks like when using the Android Studio profiler, it's not passing request data along to the server. I was able to see this by introspecting the traffic using Charles Proxy.

I'm guessing this is an issue with either Android studio or deep in our okta commons SDK. For now, I'd recommend turning off the profiler.

johnnyzen commented 2 years ago

Nice one @JayNewstrom for recreating. Will disable for now, and hopefully anyone else experiencing the same issue doesnt have to swear and curse the amount of times working out whats wrong.

johnnyzen commented 2 years ago

Hi @JayNewstrom

Google has responded, but its a bit over my head.

Can you understand this better than I can do?

Hello. Thanks for reporting this issue. It looks like the issue is on the outgoing request? Our interceptor doesn't modify any of the request headers and body, only reads.

With that being said, we have had issues before where upstream interceptors supply a body that can't be read more than once. And our profiling tool calls writeTo so it can peek at the request body, effectively "using up" the body. By the time the request gets sent out, its body is in a bad state.

I'm not familiar with Okta, but could that be a plausible explanation?

JayNewstrom commented 2 years ago

Sounds like those assumptions are correct. And the body is implemented as an input stream. Which is why it can't be read twice:

See https://github.com/okta/okta-commons-java/blob/208729d36fc72e3370fdddabe38903e291c06938/http/okhttp/src/main/java/com/okta/commons/http/okhttp/OkHttpRequestExecutor.java#L173

I don't think we want to "fix" this in our commons sdk, since what we're doing is the most efficient. But if you want it to work in your app, you could add an OkHttp Interceptor that wraps the request body with a Buffer.

johnnyzen commented 2 years ago

Thanks @JayNewstrom I will look into the interceptor, I think I use anyways, but will be interesting to see if I can find a solution.

Best wishes