maui icon indicating copy to clipboard operation
maui copied to clipboard

Intercepting requests from BlazorWebView

Open jmezach opened this issue 2 years ago • 11 comments

Description

In the documentation surrounding Blazor Hybrid apps with .NET MAUI it is stated that it not recommended to expose the access token to JavaScript running inside of the WebView. Instead it is suggested that network requests are intercepted so that the access token can be injected by the native app. This indeed seems to me to be the most secure way of doing it.

However it doesn't seem there's a good way to do this with the current API's. I've managed to get something to work on Android by wrapping the WebViewClient that is injected by the BlazorWebView control and overriding a couple of methods there, but this feels brittle as the WebViewClient provided by Blazor might override additional methods in the future. On iOS I haven't found a solution that works so far although I haven't looked into it deeply enough yet.

Public API Changes

var webView = new BlazorWebView();
webView.OnRequest += (sender, args) =>
{
  args.RequestHeaders.Add("Authorization", "Bearer " + token);
}

Intended Use-Case

It would be great if the BlazorWebView had some API that would allow us to intercept requests and inject additional request headers depending on the destination of the request for example. This would allow non-Blazor JavaScript to run inside of a Blazor Hybrid app while communicating with a backend somewhere that requires authentication.

jmezach avatar Nov 15 '22 15:11 jmezach

We are tracking supporting running a .NET MAUI Blazor app as a web app with https://github.com/dotnet/maui/issues/1069. This issue tracks providing tooling to enable authentication and an ASP.NET Core backend.

danroth27 avatar Dec 08 '22 23:12 danroth27

Hi @jmezach , I have the same problem that an extra header has to be added with every request from the BlazorWebView. It looks like it will take some time before this becomes possible in a BlazorWebView. Have you found a solution in the meantime to capture the request and add a header?

inetzo avatar Jan 21 '23 20:01 inetzo

It would also be useful to be able to block requests made by the BlazorWebview (e.g. only allow whitelisted URLs).

In a MAUI Blazor hybrid app, a BlazorWebview might be used to render UI and for security reasons it makes sense to disallow any request to external JavaScript, CSS or Images. In other cases, the WebView could only be allowed to access certain URLs.

For example, there is currently no RichTextEditor control for .Net MAUI and some people recommend using a BlazorWebview and a Blazor-based RichTextEditor instead. This WebView would not need to and should not be allowed to make arbitrary requests.

In other cases, a complex JavaScript library might be used, and one would like to make sure this library does not request or send any data.

The current mechanisms are not sufficient: Using the "BlazorWebView.UrlLoading" event can only prevent changing the page URL, but not block other requests made by the page from JavaScript.

Using a "Content-Security-Policy" in header of index.html it also only of limited use. Some libraries like the Fluent Web Components for Blazor need the "unsafe-inline" (for styles) option and libraries using WebWorkers/WASM need "unsafe-eval", data: and blob:

So, an effective way to make BlazorWebview more secure and more powerful would be to be able to intercept and selectively filter/block/modify all request the BlazorWebview wants to make.

WolfBearGames avatar Jan 22 '23 22:01 WolfBearGames

@inetzo I did manage to get something working by injecting a custom WebViewClient on Android and a custom UrlSchemeHandler on iOS through the BlazorWebViewInitialized and BlazorWebViewInitializing. Unfortunately I found out that on Android you can implement the ShouldInterceptRequest method, but it doesn't give you access to the request body so you cannot perform a full request which is quite limiting IMHO. However, that is a limitation of the Android platform, not so much of .NET MAUI nor Blazor.

jmezach avatar Jan 23 '23 12:01 jmezach

Hi @jmezach, I also tried to replace the WebViewClient within blazorWebView_BlazorWebViewInitialized via e.WebView.SetWebViewClient(new MyWebViewClient());. However, the problem is that this breaks the Blazor functionality. (The webpage at https://0.0.0.0 could not be loaded)

My MyWebViewClient looks like this:

    internal class MyWebViewClient: WebViewClient
    {

        public MyWebViewClient() 
        {
          
        }

        public override WebResourceResponse ShouldInterceptRequest(global::Android.Webkit.WebView view, IWebResourceRequest request)
        {

            request.RequestHeaders.Add("MyHeader", "MyValue");

            return base.ShouldInterceptRequest(view, request);
        }
    }

Would you mind dropping a hint on how you managed to do this?

mcl-sz avatar Feb 01 '23 15:02 mcl-sz

@mcl-sz Rather than replacing the WebViewClient, try wrapping it instead. So I created a derived WebViewClient class that accepts a WebViewClient as a constructor parameter and in the ShoudlInterceptRequest() method I do my custom logic if needed and call the inner WebViewClient (if needed). That way the Blazor specific logic should still work.

jmezach avatar Feb 06 '23 11:02 jmezach

I have spent countless hours trying to do this exact thing. The current setup is odd. Hoping this gets added.

Aeroverra avatar Mar 21 '23 02:03 Aeroverra

@mcl-sz Rather than replacing the WebViewClient, try wrapping it instead. So I created a derived WebViewClient class that accepts a WebViewClient as a constructor parameter and in the ShoudlInterceptRequest() method I do my custom logic if needed and call the inner WebViewClient (if needed). That way the Blazor specific logic should still work.

Hi @jmezach , I've tried to implement it in the same way as you mentioned here. My code eventually renders the index.html but no blazor code ever gets executed, thus it only shows the default "Loading...". My custom client looks as follows:

public class CustomAndroidWebViewClient: WebViewClient
    {
        private readonly WebViewClient _client;

        public CustomAndroidWebViewClient(WebViewClient client)
        {
            _client = client;
           
        }
        public override WebResourceResponse ShouldInterceptRequest(WebView view, IWebResourceRequest urlResource)
        {
           //Code to add custom http request header here

            return _client.ShouldInterceptRequest(view, urlResource);
        }
       
    }

I set my CustomClient in the BlazorWebViewInitialized. Any help would be appreciated!

SvenPepermans avatar May 25 '23 15:05 SvenPepermans

Hi @SvenPepermans ,

I did this:

        private class WebViewClientOverride : WebViewClient
        {
            private WebViewClient _original { get; }
            private ILogger _log { get; }

            public WebViewClientOverride(WebViewClient original, ILogger log)
            {
                _original = original;
                _log = log;
            }

            public override bool ShouldOverrideUrlLoading(WebView? view, IWebResourceRequest? request)
            {
               return _original.ShouldOverrideUrlLoading(view, request);
            }

            public override WebResourceResponse? ShouldInterceptRequest(WebView? view, IWebResourceRequest? request)
            {
                var resourceResponse = _original.ShouldInterceptRequest(view, request);
                if (resourceResponse == null)
                    return null;

                request.RequestHeaders.Add("MyHeader", "MyValue");
                return resourceResponse;
            }

            protected override void Dispose(bool disposing)
            {
                if (!disposing)
                    return;

                _original.Dispose();
            }
    }

Take a look at https://github.com/Actual-Chat/actual-chat/tree/c49659be7fbe5f174abc51053aa2a9150bcefae5/src/dotnet/App.Maui , it gave me a lot of inspiration.

mcl-sz avatar May 25 '23 15:05 mcl-sz

We've added this issue to our backlog, and we will work to address it as time and resources allow. If you have any additional information or questions about this issue, please leave a comment. For additional info about issue management, please read our Triage Process.

ghost avatar May 25 '23 17:05 ghost

Hi @SvenPepermans ,

I did this:

        private class WebViewClientOverride : WebViewClient
        {
            private WebViewClient _original { get; }
            private ILogger _log { get; }

            public WebViewClientOverride(WebViewClient original, ILogger log)
            {
                _original = original;
                _log = log;
            }

            public override bool ShouldOverrideUrlLoading(WebView? view, IWebResourceRequest? request)
            {
               return _original.ShouldOverrideUrlLoading(view, request);
            }

            public override WebResourceResponse? ShouldInterceptRequest(WebView? view, IWebResourceRequest? request)
            {
                var resourceResponse = _original.ShouldInterceptRequest(view, request);
                if (resourceResponse == null)
                    return null;

                request.RequestHeaders.Add("MyHeader", "MyValue");
                return resourceResponse;
            }

            protected override void Dispose(bool disposing)
            {
                if (!disposing)
                    return;

                _original.Dispose();
            }
    }

Take a look at https://github.com/Actual-Chat/actual-chat/tree/c49659be7fbe5f174abc51053aa2a9150bcefae5/src/dotnet/App.Maui , it gave me a lot of inspiration.

Hi @mcl-sz , I've succesfuly implemented this together with the Handler from the linked repo, my application now does properly startup, that's the first step. What I'm seeing now though, is that to all internal requests the resourceResponse has an actual value, but when i'm navigating to an actual new url within the WebView, the resourceResponse = null, thus the header does not get added. Even if i remove that null check statement the header does not get added to the http requests.

SvenPepermans avatar May 26 '23 08:05 SvenPepermans