1

I have a Windows Forms application which is consuming an OAUTH service. In order to authenticate, I use a Web Browser control to navigate to the service's login page. The user enters username and password, and this works well.

Next, the service displays a "Grant access" page. When the user clicks "Grant access", the page redirects to a URL of my choice, passing the authentication token in the query string.

This worked well on the Windows 7 machine that I developed on. I capture the query string in the Navigating event, hide the web browser control, and can then use the service by using the token I received. But on Windows 8 and Windows Server 2012, the Navigating event never happens. Instead, the Web Browser control displays a 404 error.

Is there some way that I can capture the results of that redirect?

P.S. I tried using an HttpListener, and it worked, but it requires that the application be installed by an Administrator to set up the http bindings.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • Try handling the raw `BeforeNavigate2` event on the underlying WebBrowser ActiveX, like [this](http://stackoverflow.com/a/20902928/1768303). – noseratio Mar 10 '14 at 21:50
  • @Noseratio: I'll try that. In the meantime, if you were to make an answer of that (and for WinForms), then I would upvote it. – John Saunders Mar 10 '14 at 21:59

1 Answers1

2

Try handling the raw BeforeNavigate2 event on the underlying WebBrowser ActiveX, here's a WinForms version (based on the original WPF version):

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsForms_22311110
{
    public partial class MainForm : Form
    {
        WebBrowser webBrowser;

        public MainForm()
        {
            InitializeComponent();

            this.webBrowser = new WebBrowser();
            this.webBrowser.Dock = DockStyle.Fill;
            this.Controls.Add(this.webBrowser);
            this.webBrowser.Visible = true;

            this.Load += MainForm_Load;
        }

        void MainForm_Load(object sender, EventArgs e)
        {
            // the below line makes sure the underlying ActiveX gets created
            if (this.webBrowser.Document != null || this.webBrowser.ActiveXInstance == null)
                throw new InvalidOperationException("webBrowser");

            var sink = new WebBrowserEventSink();
            sink.Connect(this.webBrowser);

            this.webBrowser.Navigate("http://example.com");            
        }
    }

    /// <summary>
    /// Handling WinForms WebBrowser ActiveX events directly
    /// this code depends on SHDocVw.dll COM interop assembly,
    /// generate SHDocVw.dll: "tlbimp.exe ieframe.dll",
    /// and add as a reference to the project
    ///
    /// by Noseratio - https://stackoverflow.com/a/22312476/1768303
    /// </summary>

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(SHDocVw.DWebBrowserEvents2))]
    public class WebBrowserEventSink : SHDocVw.DWebBrowserEvents2
    {
        System.Runtime.InteropServices.ComTypes.IConnectionPoint _sinkCP = null;
        int _sinkCookie = int.MaxValue;

        public void Connect(WebBrowser webBrowser)
        {
            if (_sinkCookie != int.MaxValue)
                throw new InvalidOperationException();

            var activeXInstance = webBrowser.ActiveXInstance;

            var cpc = (System.Runtime.InteropServices.ComTypes.IConnectionPointContainer)activeXInstance;
            var guid = typeof(SHDocVw.DWebBrowserEvents2).GUID;

            cpc.FindConnectionPoint(ref guid, out _sinkCP);
            _sinkCP.Advise(this, out _sinkCookie);
        }

        public void Disconnect()
        {
            if (_sinkCookie == int.MaxValue)
                throw new InvalidOperationException();
            _sinkCP.Unadvise(_sinkCookie);
            _sinkCookie = int.MaxValue;
            _sinkCP = null;
        }

        #region SHDocVw.DWebBrowserEvents2

        public void StatusTextChange(string Text)
        {
        }

        public void ProgressChange(int Progress, int ProgressMax)
        {
        }

        public void CommandStateChange(int Command, bool Enable)
        {
        }

        public void DownloadBegin()
        {
        }

        public void DownloadComplete()
        {
        }

        public void TitleChange(string Text)
        {
        }

        public void PropertyChange(string szProperty)
        {
        }

        public void BeforeNavigate2(object pDisp, ref object URL, ref object Flags, ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel)
        {
            MessageBox.Show("BeforeNavigate2: " + URL.ToString());
        }

        public void NewWindow2(ref object ppDisp, ref bool Cancel)
        {
        }

        public void NavigateComplete2(object pDisp, ref object URL)
        {
        }

        public void DocumentComplete(object pDisp, ref object URL)
        {
        }

        public void OnQuit()
        {
        }

        public void OnVisible(bool Visible)
        {
        }

        public void OnToolBar(bool ToolBar)
        {
        }

        public void OnMenuBar(bool MenuBar)
        {
        }

        public void OnStatusBar(bool StatusBar)
        {
        }

        public void OnFullScreen(bool FullScreen)
        {
        }

        public void OnTheaterMode(bool TheaterMode)
        {
        }

        public void WindowSetResizable(bool Resizable)
        {
        }

        public void WindowSetLeft(int Left)
        {
        }

        public void WindowSetTop(int Top)
        {
        }

        public void WindowSetWidth(int Width)
        {
        }

        public void WindowSetHeight(int Height)
        {
        }

        public void WindowClosing(bool IsChildWindow, ref bool Cancel)
        {
        }

        public void ClientToHostWindow(ref int CX, ref int CY)
        {
        }

        public void SetSecureLockIcon(int SecureLockIcon)
        {
        }

        public void FileDownload(bool ActiveDocument, ref bool Cancel)
        {
        }

        public void NavigateError(object pDisp, ref object URL, ref object Frame, ref object StatusCode, ref bool Cancel)
        {
        }

        public void PrintTemplateInstantiation(object pDisp)
        {
        }

        public void PrintTemplateTeardown(object pDisp)
        {
        }

        public void UpdatePageStatus(object pDisp, ref object nPage, ref object fDone)
        {
        }

        public void PrivacyImpactedStateChange(bool bImpacted)
        {
        }

        public void NewWindow3(ref object ppDisp, ref bool Cancel, uint dwFlags, string bstrUrlContext, string bstrUrl)
        {
        }

        public void SetPhishingFilterStatus(int PhishingFilterStatus)
        {
        }

        public void WindowStateChanged(uint dwWindowStateFlags, uint dwValidFlagsMask)
        {
        }

        public void NewProcess(int lCauseFlag, object pWB2, ref bool Cancel)
        {
        }

        public void ThirdPartyUrlBlocked(ref object URL, uint dwCount)
        {
        }

        public void RedirectXDomainBlocked(object pDisp, ref object StartURL, ref object RedirectURL, ref object Frame, ref object StatusCode)
        {
        }

        public void BeforeScriptExecute(object pDispWindow)
        {
        }

        public void WebWorkerStarted(uint dwUniqueID, string bstrWorkerLabel)
        {
        }

        public void WebWorkerFinsihed(uint dwUniqueID)
        {
        }

        #endregion
    }
}
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • I'm sure this would have worked. In fact, it showed me that I'm receiving a `NavigationError` event. It just doesn't show me _why_. I need to try watching the network, but 1) When I use Fiddler, the whole thing works, and 2) The communication is over SSL. I know that "it works when I use Fiddler" usually implies a proxy problem, but there are no proxy servers on my (home) network. – John Saunders Mar 11 '14 at 07:04
  • @JohnSaunders, do you have a legit SSL cert installed on your ISS? Something the full IE browser doesn't complain about when you dial the same URL in IE? – noseratio Mar 11 '14 at 07:08
  • It's not my IIS server. It's a commercial site. – John Saunders Mar 11 '14 at 07:11
  • @JohnSaunders, if you're willing to play with this further, try implementing [`IHttpSecurity` service](http://jiangsheng.net/2013/07/17/howto-ignoring-web-browser-certificate-errors-in-webbrowser-host). There's a shortcut to do it with [`IProfferService`](http://stackoverflow.com/a/19395092/1768303). – noseratio Mar 11 '14 at 07:19
  • It appears that the problem _may_ have been the callback URL I was using. The service is located at `https://service.com/`, and I was telling it to call me back at `https://localhost/callback/`. Changing that to `https://service.com/callback/` seems to work. – John Saunders Mar 11 '14 at 08:58
  • 1
    I was able to guess this because I got `NavigateError` on the URL which should have resulted in a redirect to my callback address. But surely there should have been a clearer indication of this? I've wasted weeks not being able to see what was happening! – John Saunders Mar 11 '14 at 09:02
  • @JohnSaunders, that's what I meant above, dialing `https://localhost/callback` in IE would probably result in an invalid SSL certificate message? Anyway, I can't think of any better way than `IHttpSecurity` or `NavigationError`. – noseratio Mar 11 '14 at 09:23
  • BTW, isn't this the same as `_browser.ActiveXInstance.NavigateError += handler`? – John Saunders Mar 18 '14 at 00:18
  • @JohnSaunders, I dealt with `BeforeNavigate2` not being fired on the generated ActiveX interop wrapper in the [linked question](http://stackoverflow.com/q/20838264/1768303). That's why I had to resort to the low level `IConnectionPoint` stuff. `NavigateError` may work without that plumbing, does it for you? – noseratio Mar 18 '14 at 00:39