OAuth Trick in coding
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/