Last night I was playing around and ended up creating a new project to the small collection of example applications for the Twitterizer library. I ended up working around a complaint that a lot of desktop application developers have about implementing OAuth: PIN-Based authentication.If you're not familiar with OAuth, there are a few different authorization processes, referred to as "flows." The two most common flows are the web flow and the device flow (aka, PIN-based authentication).

When a developer has a web application, the authorization process occurs seamlessly for the user: they are sent to the service to login, then redirected back to the application. It's really just a matter of a few clicks.

The device flow is a little less user friendly, since the service can't simply redirect the user back to the desktop application from the service's website. To handle devices, the service provides the user with a PIN (Twitter's PIN is 7 digits) and instructs the user to write it down and return to the application. While this only has to happen one time, asking the user to remember a pin and supply it to the application is not ideal.

The solution I came up with (in a WPF application) is to use the HttpListener class to create a simplistic local webserver. It ended up working better than I could have hoped. I am able to get the request token, open a browser for the user that loads the authorization url, then redirects the user back to the localhost webservice, where the desktop application is waiting. Once the user reaches it, I just grab the oauth_token and verifier values from the querystring, and exchange the request token for the access token.

It's a pretty solid proof of concept and I'm sure there will be a few people that will find it worthwhile.

If I get enough interest in it, I might see if I can work the whole thing into Twitterizer, maybe as an addon.

Here is the code to the main window. The only code that isn't included is a TokenHolder class that acts as a threadsafe singleton. It was the quickest way I could get around a multithreading issue without learning a lot about WPF (I plan on learning it, but I didn't want to hold up the example until then).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
namespace TwitterizerDesktop2
{
    using System;
    using System.Diagnostics;
    using System.Net;
    using System.Windows;
    using Twitterizer;

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private string ConsumerKey = "GoNLHcoS2tkG0rJNBgwMfg";
        private string ConsumerSecret = "9j4hqpKxntK6IbrrsG1RX69XzU3RssJE5rDKtWq9g";

        public static OAuthTokenResponse accessTokenResponse;

        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Handles the Click event of the button1 control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            // Create a localhost address with a random port
            string listenerAddress = string.Format("http://localhost:{0}/", new Random().Next(1000, 10000));

            // Setup the httplistener class
            HttpListener listener = new HttpListener();
            listener.Prefixes.Add(listenerAddress);
            listener.Start();

            // Set our callback method
            IAsyncResult asyncResult = listener.BeginGetContext(HttpListener_Callback, listener);

            // Get the request token
            string requestToken = OAuthUtility.GetRequestToken(ConsumerKey, ConsumerSecret, listenerAddress).Token;

            // Send the user to Twitter to login and grant access
            Process.Start(new ProcessStartInfo()
            {
                FileName = OAuthUtility.BuildAuthorizationUri(requestToken).AbsoluteUri
            });

            // Wait for an event
            asyncResult.AsyncWaitHandle.WaitOne();

            // Put the application in a loop to wait for the async process to *really* finish processing
            DateTime timeout = DateTime.Now.AddMinutes(2);
            while (!asyncResult.IsCompleted || TokenHolder.Instance.AccessTokenResponse == null)
            {
                if (DateTime.Now > timeout)
                    return;
            }

            // Display the group box
            groupBox1.Visibility = System.Windows.Visibility.Visible;

            // Show the token values
            AccessTokenTextBlock.Text = string.Format(
                "{0} / {1}",
                TokenHolder.Instance.AccessTokenResponse.Token,
                TokenHolder.Instance.AccessTokenResponse.TokenSecret);

            // Show the user values
            UserTextBlock.Text = string.Format(
                "{0} - {1}",
                TokenHolder.Instance.AccessTokenResponse.UserId,
                TokenHolder.Instance.AccessTokenResponse.ScreenName);

            // Try to bring the window to the foreground
            this.Activate();
        }

        protected void HttpListener_Callback(IAsyncResult result)
        {
            HttpListener listener = (HttpListener)result.AsyncState;

            // Call EndGetContext to complete the asynchronous operation.
            HttpListenerContext context = listener.EndGetContext(result);
            HttpListenerRequest request = context.Request;
            string token = request.QueryString["oauth_token"];
            string verifier = request.QueryString["oauth_verifier"];

            // Obtain a response object.
            HttpListenerResponse response = context.Response;

            string responseString = "<HTML><BODY>You have been authenticated. Please return to the application. <script language=\"javascript\">window.close();</script></BODY></HTML>";
            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);

            response.ContentLength64 = buffer.Length;
            System.IO.Stream output = response.OutputStream;
            output.Write(buffer, 0, buffer.Length);
            output.Close();

            response.Close();

            listener.Close();

            TokenHolder.Instance.AccessTokenResponse = OAuthUtility.GetAccessToken(
                  ConsumerKey,
                  ConsumerSecret,
                  token,
                  verifier);
        }
    }
}

Here is a link to the project files on Twitterizer's code repository: http://code.google.com/p/twitterizer/source/browse/trunk/ExampleApplications/TwitterizerDesktop2/