10

Or How to inject a custom header into the initial request to a site when new-ing up an instance of the ChromiumWebBrowser.

I'm a noob with Chromium and could really use some help. I have a winforms app with a CEF window. K, no prob so far. What I need to do is to call/load the initial url with a custom http-header that contains authentication info. Is this possible?

The following is essentially what is at play and all parts work except the custom header (Doh!)

Winform(CEF httpRequest(with custom header)) [never gets past this point]=> C# MVC web app => Owin_Authentication_Pipeline segment => MVC Response with populated Razor view => Shows up in Winform Chromium app.

Maybe this will help as well:

using CefSharp;
using CefSharp.WinForms;
...
private void Form1_Load(object sender, EventArgs e)
{
    Cef.Initialize();
    ChromiumWebBrowser myBrowser = new ChromiumWebBrowser("whatever.com");
    // ??How do i get a custom header be sent with the above line??

    myBrowser.Dock = DockStyle.Fill;
    //myBrowser.ShowDevTools();
    //myBrowser.RequestHandler = new DSRequestHander();
    //myBrowser.FrameLoadStart += myBrowser_FrameLoadStart;
    this.Controls.Add(myBrowser);
}

I Groggled this to death, looked, tried all the tricks in my toolbox and then some.

Any ideas, help or hints on how I might be able to solve or get around this boggler is greatly appreciated. Thanks in advance.

JackJack
  • 101
  • 1
  • 1
  • 6
  • 1
    Are you using the CEFSharp project? https://github.com/cefsharp/CefSharp/wiki – NinjaMid76 Jul 06 '15 at 16:22
  • @NinjaMid76 Yes, yes i am. – JackJack Jul 07 '15 at 17:11
  • It's next to impossible to help you if you don't share more info e.g. on how far in this chain of components your custom header gets. Does it appear at the Owin_Authentication_Pipeline segment? Does it reject it? Etc. etc. Provide us with some basic debugging info of what you have tried. Good stackoverflow questions have an [MVCE](http://stackoverflow.com/help/mcve) – jornh Jul 09 '15 at 04:43
  • @jornh ..chill. no need for a flair of denigration. And 'no' it does not reach the owin pipeline. The question is how to inject a custom header into the initial request to a site when new-ing up a ChromiumWebBrowser. I have edited the question to clarify. Thank you for you feedback. Cheers...it's all good. – JackJack Jul 10 '15 at 14:31
  • No intention to denigrate at all - just want to take out as much guesswork out to better be able to offer help :-). Your edits greatly helped with that! Have you tried something along the lines of https://groups.google.com/forum/m/#!topic/cefsharp/Yi9bWHmASQU ? That post is a bit dated now, not sure if the API has changed since then. – jornh Jul 10 '15 at 19:09
  • The `API` has changed a little, pretty much the same concept though. I split `Request` and `Response` out into two separate objects quite some time ago. At some point we should probably wrap the underlying `HeaderMap` object to provide for a slightly nicer `API`. – amaitland Jul 12 '15 at 11:17
  • Although this project has morphed away from the CEFSharp project, I am going to work on a simple proof of concept (if not already done) and push it out to Github for anyone who might be interested. Thanks to all that have commented on this question. -felixd – JackJack Mar 23 '17 at 00:15

3 Answers3

14

Updated to reflect major Chromium changes

Updated to reflect changes made in version 75 (should work in 75 and newer)

The method you're after should be OnBeforeResourceLoad, a basic example should look like:

public class CustomResourceRequestHandler : ResourceRequestHandler
{
    protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
    {
        var headers = request.Headers;
        headers["User-Agent"] = "My User Agent";
        request.Headers = headers;

        return CefReturnValue.Continue;
    }
}

public class CustomRequestHandler : RequestHandler
{
    protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
    {
        return new CustomResourceRequestHandler();
    }
}

browser.RequestHandler = new CustomRequestHandler();

Using the IRequest.Headers property you must read the headers property, make changes then reassign it. It's now possible to use the SetHeaderByName/GetHeaderByName functions to get/set a single header.

Neil Moss
  • 6,065
  • 2
  • 23
  • 38
amaitland
  • 2,700
  • 1
  • 20
  • 50
  • I created a CustomChromeBrowser class inheriting from ChromiumWebBrowser and assign a RequestHandler class (comes with demo project) via Initialized event of the CustomChromeBrowser class. – CodingYourLife May 19 '16 at 12:03
  • All the `Handlers` are simple properties, assigning them in the constructor would be sufficient. – amaitland May 19 '16 at 13:09
  • Can we have more details please, I've the same issue and I'm not sure to understand how to do what you suggest. – Psddp Dec 14 '16 at 23:38
  • This isn't working for me. The Headers property of the request just isn't changing. No exception is being thrown, it just won't update. Headers.IsReadOnly is false. – scatter Jul 10 '18 at 04:26
  • @amaitland Will this automatically apply to page requests and ajax requests? I am mainly wondering about changing the ajax requests and how to accomplish that. It looks like it should because i don't see other ways to accomplish scanning the API Doc. Anyway a word from you will kind of make my design decision and keep me from going other directions. – Carter May 28 '20 at 22:13
  • 1
    All requests directly related to the browser should flow through here. You can use http://cefsharp.github.io/api/81.3.x/html/M_CefSharp_IRequestContextHandler_GetResourceRequestHandler.htm for RequestContext level handling. If you are having trouble isolating just the Ajax requests please ask a new question. – amaitland May 29 '20 at 19:16
  • 1
    See https://github.com/cefsharp/CefSharp/wiki/General-Usage#request-context-browser-isolation for background details for RequestContext. From memory requests from Service workers will only be accessible through the RequestContext level handling as they aren't necessarily directly associated with a browser. – amaitland May 29 '20 at 19:21
  • @amaitland Thanks! you do a great job on CefSharp. Working like a charm thanks for all you do. +3 – Carter Jun 01 '20 at 14:43
6

You should create a class that implement IRequestHandler then set an instance of that class as RequestHandler in your browser object.

With version 53, that class should look like:

class ChromeBrowserRequestHandler: IRequestHandler
    {
        public bool GetAuthCredentials(IWebBrowser browserControl, IBrowser browser, IFrame frame, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
        {
            return false;
        }

        public bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool isRedirect)
        {
            return false;
        }

        public bool OnBeforePluginLoad(IWebBrowser browser, string url, string policyUrl, WebPluginInfo info)
        {
            return false;
        }

        public CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
        {
            var headers = request.Headers;
            headers["Custom-Header"] = "My Custom Header";
            request.Headers = headers;

            return CefReturnValue.Continue;
        }

        public bool OnCertificateError(IWebBrowser browser, CefErrorCode errorCode, string requestUrl)
        {
            return false;
        }

        public void OnPluginCrashed(IWebBrowser browser, string pluginPath)
        {
        }

        public void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status)
        {
        }

        public IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) 
        { 
            return null; 
        }

        public bool OnCertificateError(IWebBrowser browserControl, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback) 
        {
            return false; 
        }

        public bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) 
        {
            return false; 
        }

        public void OnPluginCrashed(IWebBrowser browserControl, IBrowser browser, string pluginPath) 
        {
        }

        public bool OnProtocolExecution(IWebBrowser browserControl, IBrowser browser, string url) 
        {
            return false;
        }

        public bool OnQuotaRequest(IWebBrowser browserControl, IBrowser browser, string originUrl, long newSize, IRequestCallback callback) 
        {
            return false;
        }

        public void OnRenderViewReady(IWebBrowser browserControl, IBrowser browser) 
        { 
        }

        public void OnResourceLoadComplete(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength)
        { 
        }

        public void OnResourceRedirect(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, ref string newUrl) 
        { 
        }

        public bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) 
        {
            return false;
        }
    }

Then, while creating your browser object:

ChromiumWebBrowser myBrowser = new ChromiumWebBrowser("whatever.com")
{
    RequestHandler = new ChromeBrowserRequestHandler()
};

Note that the request handler must be set before loading the page. If you can't set the request handler during instanctiaction, you can still set it later a reload the page with myBrowser.Load("whatever.com") .

Psddp
  • 841
  • 10
  • 16
5

In the one of the latest versions some callbacks have been moved from IRequestHandler to IResourceRequestHandler interface. Simplest way is to override default implementations RequestHandler & ResourceRequestHandler, for example:

class BearerAuthResourceRequestHandler : ResourceRequestHandler
    {
        public BearerAuthResourceRequestHandler(string token)
        {
            _token = token;
        }

        private string _token;

        protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
        {
            if (!string.IsNullOrEmpty(_token))
            {
                var headers = request.Headers;
                headers["Authorization"] = $"Bearer {_token}";
                request.Headers = headers;
                return CefReturnValue.Continue;
            }
            else return base.OnBeforeResourceLoad(chromiumWebBrowser, browser, frame, request, callback);
        }

    }
    class BearerAuthRequestHandler : RequestHandler
    {
        public BearerAuthRequestHandler(string token)
        {
            _token = token;
        }

        private string _token;

        protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
        {
            if (!string.IsNullOrEmpty(_token)) return new BearerAuthResourceRequestHandler(_token);
            else return base.GetResourceRequestHandler(chromiumWebBrowser, browser, frame, request, isNavigation, isDownload, requestInitiator, ref disableDefaultHandling);
        }
    }

Then, assign it to browser RequestHandler:

Browser.RequestHandler = new BearerAuthRequestHandler(token);