openid / AppAuth-Android

Android client SDK for communicating with OAuth 2.0 and OpenID Connect providers.
https://openid.github.io/AppAuth-Android
Apache License 2.0
2.79k stars 877 forks source link

How to make AppAuth work with http configuration? #783

Open nerdy-plutonian-ng opened 2 years ago

nerdy-plutonian-ng commented 2 years ago

Description

My app works as expected with https configuration. However when i switch to http configuration, i can log in and get redirected to my app yet right away i get this error,

E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1 Process: com.pluto.persolrevenuemanager, PID: 23593 java.lang.RuntimeException: An error occured while executing doInBackground() at android.os.AsyncTask$3.done(AsyncTask.java:300) at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355) at java.util.concurrent.FutureTask.setException(FutureTask.java:222) at java.util.concurrent.FutureTask.run(FutureTask.java:242) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) at java.lang.Thread.run(Thread.java:841) Caused by: java.lang.IllegalArgumentException: only https connections are permitted at net.openid.appauth.Preconditions.checkArgument(Preconditions.java:116) at net.openid.appauth.connectivity.DefaultConnectionBuilder.openConnection(DefaultConnectionBuilder.java:51) at net.openid.appauth.AuthorizationService$TokenRequestTask.doInBackground(AuthorizationService.java:593) at net.openid.appauth.AuthorizationService$TokenRequestTask.doInBackground(AuthorizationService.java:563) at android.os.AsyncTask$2.call(AsyncTask.java:288) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)  at java.lang.Thread.run(Thread.java:841) 

I implemented it in flutter as well and have "allowInsecureConnections" set to "true". I have this error now,

PlatformException(authorize_and_exchange_code_failed, Failed to authorize: [error: null, description: Network error], null, null)

I don't understand the network error part because i have no connection issues

agologan commented 2 years ago

By the looks of it you're using Flutter so you should reach out to the plugin creator. To work with http connections in testing environments you need two things: • a Connection builder that allows a HTTP connection like ConnectionBuilderForTesting.java • enable setSkipIssuerHttpsCheck as explained in the README.md

nerdy-plutonian-ng commented 2 years ago

Thanks. The app is originally in java though and faces the same issue. I would prefer the

By the looks of it you're using Flutter so you should reach out to the plugin creator. To work with http connections in testing environments you need two things: • a Connection builder that allows a HTTP connection like ConnectionBuilderForTesting.java • enable setSkipIssuerHttpsCheck as explained in the README.md

Thanks. The app is in java. I did it in flutter as well like you mentioned. Both have the issue. The java implementation is my priority however.

agologan commented 2 years ago

@nerdy-plutonian-ng I'm sorry, was there a follow-up to your statement? Have you resolved your inquiry?

ryanholden8 commented 7 months ago

By the looks of it you're using Flutter so you should reach out to the plugin creator. To work with http connections in testing environments you need two things: • a Connection builder that allows a HTTP connection like ConnectionBuilderForTesting.java • enable setSkipIssuerHttpsCheck as explained in the README.md

This was a very helpful comment to allow us to test our login screen via a UI test, thank you!

This is what we did in our Kotlin app:

AppAuthConfiguration
    .Builder()
    .setConnectionBuilder { uri ->
        URL(uri.toString()).openConnection() as HttpURLConnection
    }
    .setSkipIssuerHttpsCheck(true)
    .build()

This allowed us to route token requests to our okhttp3.MockWebServer which is only http. You can configure the mock web server to use https but it's just not simple. I was surprised setSkipIssuerHttpsCheck was not respected in the default ConnectionBuilder or that if we already have setSkipIssuerHttpsCheck why the ConnectionBuilder is also even checking. Seems like a duplication of responsibility.

This is how we created the different token response for our mock web server

fun createAuthenticatedCodeResponse(): AuthorizationResponse {
   // OAuthConfig is our own model that defines our app's oauth config
    val oAuthConfig = OAuthConfig(
        // Ensure we don't reach out to the real server during our tests
        baseUri = Uri.parse(getFromKoin<BaseUrlProvider>().toString()),
    )
    val config = AuthorizationServiceConfiguration(oAuthConfig.authCodeEndpoint, oAuthConfig.tokenEndpoint)

    val authRequest = AuthorizationRequest
        .Builder(config, oAuthConfig.clientId, ResponseTypeValues.CODE, oAuthConfig.redirectUri)
        .build()

    return AuthorizationResponse
        .Builder(authRequest)
        .setAuthorizationCode("test")
        .build()
}

fun createAuthenticatedBearTokenResponse(): AuthorizationResponse {
   // OAuthConfig is our own model that defines our app's oauth config
    val oAuthConfig = OAuthConfig(
        // Ensure we don't reach out to the real server during our tests
        baseUri = Uri.parse(getFromKoin<BaseUrlProvider>().toString()),
    )

    val config = AuthorizationServiceConfiguration(oAuthConfig.authCodeEndpoint, oAuthConfig.tokenEndpoint)

    val authRequest = AuthorizationRequest
        .Builder(config, oAuthConfig.clientId, ResponseTypeValues.CODE, oAuthConfig.redirectUri)
        .build()

    return AuthorizationResponse
        .Builder(authRequest)
        .setTokenType(AuthorizationResponse.TOKEN_TYPE_BEARER)
        .setAccessToken("test2")
        .setAccessTokenExpiresIn(100000)
        .build()
}

Then we had to override ActivityResultRegistryOwner to complete right away instead of navigating to a browser:

GlobalContext.get().declare<ActivityResultRegistryOwner>(
    object : ActivityResultRegistryOwner {
        override val activityResultRegistry = object : ActivityResultRegistry() {
            override fun <I : Any?, O : Any?> onLaunch(
                requestCode: Int,
                contract: ActivityResultContract<I, O>,
                input: I,
                options: ActivityOptionsCompat?,
            ) {
                // don't launch an activity, just respond that it worked
                dispatchResult(requestCode, Activity.RESULT_OK, createAuthenticatedCodeResponse().toIntent())
            }
        }
    },
)