hotwired / turbo-android

Android framework for making Turbo native apps
MIT License
407 stars 51 forks source link

Access to fetch at `external_redirect_url` from origin `our_url` has been blocked due to CORS policy #320

Closed hjhart closed 3 months ago

hjhart commented 3 months ago

Expected behavior:

When tapping on a link that redirects to an external site The external site should load correctly

Actual behavior:

Screenshot 2024-03-13 at 3 39 44 PM

"Error loading page" screen

Along with this in the Logcat output:

WebFragment onVisitErrorReceived() error code 0
[INFO:CONSOLE(0)] Access to fetch at 'https://our-app.formstack.com/forms/redacted' 
(redirected from 'http://app.android-local.dev-hellobrightline.com:3000/formstack/forms/3f76559e-bb49-4465-a7ab-ab4e75b80f29') 
from origin 'http://app.android-local.dev-hellobrightline.com:3000' has been blocked by CORS policy:
 No 'Access-Control-Allow-Origin' header is present on the requested resource. 
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.", 
source: http://app.android-local.dev-hellobrightline.com:3000/formstack/forms/3f76559e-bb49-4465-a7ab-ab4e75b80f29 (0)

Isolating the problem further, if instead I have the original link pointing to https://our-app.formstack.com/forms/redacted instead of http://app.android-local.dev-hellobrightline.com:3000/formstack/forms/3f76559e-bb49-4465-a7ab-ab4e75b80f29, it works just fine. The introduction of the redirect seems to break it.

I noticed the CORS error in firefox and chrome, and disabling turbo fixed the request since it stopped being a fetch via turbo.js. Now it's happily a link that redirects successfully. But in our turbo-android app, it is still failing.

Since I don't have control over the formstack servers, I cannot modify the CORS headers successfully.

I'm going to continue to dive into this issue, but wanted to post it here for posterity, and to hopefully get some help from anyone who has encountered this issue previously.

Thanks in advance.

MichalSznajder commented 3 months ago

You have to intercept navigations to external links and create a separate WebView to show them. WebView from Turbo is only intended to be used with turbo.js itself.

hjhart commented 3 months ago

Thanks @MichalSznajder !

I already have some code doing that here:

interface NavDestination : TurboNavDestination {

   override fun shouldNavigateTo(newLocation: String): Boolean {
        Log.d("Navigation", "Navigation to ${newLocation} from ${location}")
        return when (isNavigable(newLocation)) {
            true -> true
            else -> {
                launchCustomTab(newLocation)
                false
            }
        }
    }

    private fun isNavigable(location: String): Boolean {
        return location.startsWith(BuildConfig.BASE_APP_URL)
    }

}

But the logging (Navigation to ${newLocation} from ${location}") only shows up on the first link.

So it shows up for my internal link, returns true, and continues within the WebView with turbo.js. However, it does not log the above after the redirect. So, that mechanism fails here. It's very possible I'm intercepting the requests at the wrong point in code, but if not, this is more internal to the turbo-android codebase than I've dug into.

I think we're getting closer. I'll keep looking through the docs to better understand if I'm intercepting the request properly.

hjhart commented 3 months ago

I think I've verified that this is a bug in turbo-android. I've added this commit to a fork of the demo repository:

https://github.com/hjhart/turbo-native-demo/commit/767139cdc09d6c4181dd357860038b39ab65f742

You can see how the external links work just fine, but a redirect to an external link does now. See the video below, running against a local server and using the default turbo-android demo code.

https://github.com/hotwired/turbo-android/assets/547981/201cd83e-c7c4-4549-9257-3f7ebfcaa532

This is what shows up in LogCat at the time:

2024-03-27 13:50:28.499 18141-18380 TurboLog                dev.hotwire.turbo.demo               D  visitProposedToLocation ........... [session: main, location: https://506f8e14c2c2.ngrok.app/follow-external-redirect, options: TurboVisitOptions(action=ADVANCE, snapshotHTML=null, response=null)]
2024-03-27 13:50:28.506 18141-18141 TurboLog                dev.hotwire.turbo.demo               D  shouldNavigateToLocation .......... [session: main, location: https://506f8e14c2c2.ngrok.app/follow-external-redirect, shouldNavigate: true, currentFragment: WebHomeFragment]
2024-03-27 13:50:28.511 18141-18141 TurboLog                dev.hotwire.turbo.demo               D  navigate .......................... [session: main, location: https://506f8e14c2c2.ngrok.app/follow-external-redirect, options: TurboVisitOptions(action=ADVANCE, snapshotHTML=null, response=null), currentContext: DEFAULT, newContext: DEFAULT, presentation: PUSH, currentFragment: WebHomeFragment]
2024-03-27 13:50:28.511 18141-18141 TurboLog                dev.hotwire.turbo.demo               D  navigateWithinContext ............. [session: main, location: https://506f8e14c2c2.ngrok.app/follow-external-redirect, presentation: PUSH, currentFragment: WebHomeFragment]
2024-03-27 13:50:28.547 18141-18141 TurboLog                dev.hotwire.turbo.demo               D  navigateToLocation ................ [session: main, location: https://506f8e14c2c2.ngrok.app/follow-external-redirect, uri: turbo://fragment/web, currentFragment: WebHomeFragment]
2024-03-27 13:50:28.564 18141-18141 ImeTracker              dev.hotwire.turbo.demo               I  dev.hotwire.turbo.demo:d0f1ad: onRequestHide at ORIGIN_CLIENT_HIDE_SOFT_INPUT reason HIDE_SOFT_INPUT
2024-03-27 13:50:28.565 18141-18141 StradaLog               dev.hotwire.turbo.demo               D  bridgeDestinationDidStop .......... [https://506f8e14c2c2.ngrok.app]
2024-03-27 13:50:28.565 18141-18141 TurboLog                dev.hotwire.turbo.demo               D  fragment.onStop ................... [session: main, location: https://506f8e14c2c2.ngrok.app, fragment: WebHomeFragment]
2024-03-27 13:50:28.583 18141-18141 TurboLog                dev.hotwire.turbo.demo               D  fragment.onViewCreated ............ [session: main, location: https://506f8e14c2c2.ngrok.app/follow-external-redirect, fragment: WebFragment]
2024-03-27 13:50:28.588 18141-18141 TurboLog                dev.hotwire.turbo.demo               D  fragment.onStart .................. [session: main, location: https://506f8e14c2c2.ngrok.app/follow-external-redirect, fragment: WebFragment]
2024-03-27 13:50:28.590 18141-18141 StradaLog               dev.hotwire.turbo.demo               D  bridgeDestinationDidStart ......... [https://506f8e14c2c2.ngrok.app/follow-external-redirect]
2024-03-27 13:50:28.610 18141-18141 TurboLog                dev.hotwire.turbo.demo               D  visitLocation ..................... [session: main, location: https://506f8e14c2c2.ngrok.app/follow-external-redirect, options: TurboVisitOptions(action=ADVANCE, snapshotHTML=null, response=null), restorationIdentifier: ]
2024-03-27 13:50:28.647 18141-18380 TurboLog                dev.hotwire.turbo.demo               D  visitStarted ...................... [session: main, location: https://506f8e14c2c2.ngrok.app/follow-external-redirect, visitIdentifier: 094d240b-cbb3-459c-9c78-73bd5400bb5b, visitHasCachedSnapshot: false, visitIsPageRefresh: false]
2024-03-27 13:50:28.650 18141-18380 TurboLog                dev.hotwire.turbo.demo               D  visitRequestStarted ............... [session: main, visitIdentifier: 094d240b-cbb3-459c-9c78-73bd5400bb5b]
2024-03-27 13:50:28.741 18141-18141 StradaLog               dev.hotwire.turbo.demo               D  bridgeDestinationDidDestroy ....... [https://506f8e14c2c2.ngrok.app]
2024-03-27 13:50:28.811 18141-18141 chromium                dev.hotwire.turbo.demo               I  [INFO:CONSOLE(0)] "Access to fetch at 'https://turbo.hotwired.dev/' (redirected from 'https://506f8e14c2c2.ngrok.app/follow-external-redirect') from origin 'https://506f8e14c2c2.ngrok.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.", source: https://506f8e14c2c2.ngrok.app/follow-external-redirect (0)
2024-03-27 13:50:28.811 18141-18380 TurboLog                dev.hotwire.turbo.demo               D  visitRequestFailedWithStatusCode .. [session: main, visitIdentifier: 094d240b-cbb3-459c-9c78-73bd5400bb5b, visitHasCachedSnapshot: false, error: UnknownError(statusCode=0, reasonPhrase=null)]
2024-03-27 13:50:28.815 18141-18141 Compatibil...geReporter dev.hotwire.turbo.demo               D  Compat change id reported: 210923482; UID 10191; state: ENABLED
2024-03-27 13:50:28.841 18141-18380 TurboLog                dev.hotwire.turbo.demo               D  visitRequestFinished .............. [session: main, visitIdentifier: 094d240b-cbb3-459c-9c78-73bd5400bb5b]
hjhart commented 3 months ago

@jayohms I'm happy to take this work on, as it is blocking my companies critical path, but do you have any pointers as to what code I might want to touch before I dive into this work?

jayohms commented 3 months ago

@hjhart I dug into this and it's is an issue in both turbo-android and turbo-ios, so we'll need fixes in both libraries. See the PR with details: #325

Due to the CORS policy in turbo.js fetch requests, all the adapters see external redirects as request failures, but the linked PR handles the issue in a similar but different way than the default browser adapter. Can you give it a try and see if it resolves your issue?

hjhart commented 3 months ago

@jayohms, wow, that works perfectly. Thanks so much!

https://github.com/hotwired/turbo-android/assets/547981/46b22503-4ce2-4586-8db8-d6b9d64abdfe

jayohms commented 3 months ago

The fix is now available in 7.1.2: https://github.com/hotwired/turbo-android/releases/tag/7.1.2

hjhart commented 3 months ago

Hey @jayohms, thanks for the quick release! Does this also require an upgrade of turbo.js, or turbo-rails?

hjhart commented 3 months ago

Edit: Opened up another issue https://github.com/hotwired/turbo-android/issues/327