hotwired / turbo-android

Android framework for making Turbo native apps
MIT License
424 stars 50 forks source link

Authentication using Authorization header #280

Closed izakvdhoven closed 1 year ago

izakvdhoven commented 1 year ago

Our project requires setting of an Authorization header in order to identify users.

The way this would traditionally be done using web views is by overriding the WebviewClient's shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean function. However, TurboWebViewClient is private to the lib and cannot be inherited from.

Do you have any recommendations on setting custom headers on the requests performed by Turbo's underlaying web view?

hjhart commented 1 year ago

I also have this question. @izakvdhoven did you figure anything out?

The architecture I'm building out is suboptimal by having a Fragment that looks like this:

@TurboNavGraphDestination(uri = "turbo://fragment/family")
class FamilyFragment : TurboFragment(), NavDestination {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            setContent {
                val mUrl = "${BuildConfig.BASE_APP_URL}/family"
                val headers = HashMap<String, String>()
                headers["Authorization"] = "Bearer $token"
                AndroidView(factory = {
                    WebView(it).apply {
                        layoutParams = ViewGroup.LayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT,
                            ViewGroup.LayoutParams.MATCH_PARENT
                        )
                        webViewClient = WebViewClient()
                        loadUrl(
                            mUrl,
                            headers
                        )

                        settings.javaScriptEnabled = true
                    }
                }, update = {
                    it.loadUrl(
                        mUrl,
                        headers
                    )
                })
            }
        }
    }
}

Does anyone at 37signals have any advice on this? cc @jayohms perhaps? Thanks in advance!

jayohms commented 1 year ago

Do you use a fully native or WebView login flow? The recommended way to authenticate users is to use Android's CookieManager to set authentication cookies, which will automatically be sent along with every WebView request. If you have a fully native login flow, consider hitting a JSON endpoint to retrieve the cookies that should be set in the CookieManager/WebView.

I'd be curious, though, if that doesn't meet your needs for some reason.

izakvdhoven commented 1 year ago

Our project specifically required authentication using an OAuth token and my original question was primarily raised due to the fact that there is a way to achieve this on iOS, but not on Android.

I have used the cookie manager approach in an earlier project and can vouch for it.

jayohms commented 1 year ago

On turbo-ios, yes it's possible to set custom headers for an initial full page load, but I don't believe that's possible for all subsequent resource and javascript visit requests. The documentation outlines how to set cookies from an existing OAuth token.

turbo-android's WebViewClient handles a lot of edge cases and is non-trivial, so that can't be realistically opened up for apps to implement themselves. Allowing custom headers for every javascript visit that Turbo initiates would require a lot of internal changes that are avoided by using cookies. Is there a reason you can't use OAuth + cookies that are derived from your OAuth token?

hjhart commented 1 year ago

Okay, that's great to know @jayohms - thanks for the heads up. I am doing something similar with sessions being set based off of a jwt, but I like the cookie approach better. For anyone looking to get more information on what exactly to do, I found this article which lays out the implementation of using cookies really well (not for android, but from the request and rails side of things).

https://pragmaticstudio.com/tutorials/rails-session-cookies-for-api-authentication

hjhart commented 1 year ago

@jayohms can you provide a code example of what you might return in an rails-specific API authentication endpoint? Would I be returning request.cookies["_reef_session"] if our application name is Reef::Application?

Edit: I was thinking you'd need to respond it explicitly in the response body, but have since realized that the API response where you login will already have the headers Set-Cookie with the value you're looking for. 🤦

hjhart commented 1 year ago

@izakvdhoven can this issue be closed now?