17 Oct 2016

3-legged OAuth on desktop apps (C# & WinForm)

Default blog image

Edit

This post is outdated, please check this updated version


Original version

If you don't know OAuth or the differences between 2-legged or 3-legged authentication on Forge, please review this webinar.

It's quite straightforward to implement OAuth on a web app: as everything is on the browser, including the redirect callback, it's almost natural. What about a desktop application? In this case is not quite direct to handle callbacks or implement micro servers to handle it.

This code is almost an hack, but works for our purposes. Note this implementation is not safe, meaning your developer ID and secret can be easily hacked/read by almost any end-user. The safest way still to perform authorization on server-side. 

Let's use the System.Windows.Forms.WebBrowser, which works fine, but it doesn't handle error in the way we want for this sample. At the bottom of this post you'll find the full WebBrowser2 implementation copied from here.

Now on your app, create a form (assuming a WinForm application). Add a WebBrowser2 control, say wb. Prepare the Authorize URL (using your client ID, redirect URL and Scope) and navigate to this page. The end-user will be redirected to the Autodesk login page. When done, it will redirect to your callback URL, which is not possible or doesn't exist (at this sample, fake.com). This will throw a 404 error that we can capture. Voilà! Finally capture the code parameter of the query string and call the gettoken endpoint (e.g. using RestSharp

[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public partial class oAuthForm : Form
{
  private const string FORGE_CLIENT_ID = "XxXxXxXxXx";
  private const string FORGE_CLIENT_SECRET = "XxXxXxXx";
  private const string FORGE_CALLBACK_URL = "http://mycompanyname/api/forge/callback/oauth";
  private const string FORGE_BASE_URL = "https://developer.api.autodesk.com";
  private const string FORGE_SCOPE = "data:read data:write data:create data:search bucket:create bucket:read bucket:update bucket:delete"; // assuming a full scope

  private WebBrowser2 wb = new WebBrowser2();

  public oAuthForm()
  {
    InitializeComponent();

    wb.Dock = DockStyle.Fill;
    wb.NavigateError += new WebBrowserNavigateErrorEventHandler(wb_NavigateError);
    Controls.Add(wb);

    // this is a basic code sample, quick & dirty way to get the Authentication string
    string authorizeURL = BASE_URL + string.Format(
        "/authentication/v1/authorize?response_type=code&client_id={0}&redirect_uri={1}&scope={2}", 
        FORGE_CLIENT_ID, FORGE_CALLBACK_URL, System.Net.WebUtility.UrlEncode(FORGE_SCOPE));
        
    // now let's open the Authorize page.
    wb.Navigate(authorizeUrl);
  }

  private void wb_NavigateError(
      object sender, WebBrowserNavigateErrorEventArgs e)
  {
    // This will track errors: we want to track the 404 when the login
    // page redirects to our callback URL, let's check if is the error
    // we're tracking.
    Uri callbackURL = new Uri(e.Url);
    if (e.Url.IndexOf(FORGE_CALLBACK_URL) == -1)
    {
      MessageBox.Show("Sorry, the authorization failed", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
      return;
    }
       
    // extract the code
    var query = HttpUtility.ParseQueryString(callbackURL.Query);
    string code = query["code"];
    
    // now we have the code, let's make a Http call to 
    // /authentication/v1/gettoken 
    // and get the access_token...
    
    // you can use RestSharp for it, but I'll stop the sample here

    // you may want to close this form..
    this.Close();
  }
} 

Custom implementation of WebBrowser, let's call it WebBrowser2 (original post). Due our HTML/JavaScript implementation on the login page, some features are not natively supported by the WebBrowser (which, by default, have some features disables). To improve it, we need a set of registry keys (user level), which are well described at this stackoverflow answer

Related Article