Over the past week I’ve been playing a bit with JavaFX. I will blog on my experience later. Today I would like to follow up on my last post and write about the OAuth callback.
If you look at the sample code in my last blog, there is one ugly thing – users have to be told to press Enter once they authenticate in a browser that the application opens:
// open the browser at the authorization URL to let user authorize Desktop.getDesktop().browse(new URI(URL_AUTHORIZE + "?oauth_token=" + response.auth.token.id)); // wait for the user to authenticate System.out.println("Once you authenticated with SmugMug and granted" + "permissions to this app, press Enter to continue."); System.in.read();
Although the OAuth specification mentions one can pass a callback URL to the authorization call, I had not been able to make it work with SmugMug until recently, when I ran into this post on the SmugMug API forum. The post says that instead of accepting the oauth_callback as the query parameter in the authorization request, SmugMug allows me to set the callback URL for my application by editing the properties of the API key I’ve received in the SmugMug control panel. So, I went ahead and tried to set it to “http://localhost:9097/oauth” and it worked! So I was able to get rid of that ugly waiting for users pressing the Enter key with the following trick:
synchronized (App.class) { // create an instance of the lightweight HTTP server on port 9097 HttpServer server = HttpServer.create(new InetSocketAddress(9097), 0); // assign a handler to "/oauth" context server.createContext("/oauth", new HttpHandler() { public void handle(HttpExchange t) throws IOException { // notify the app the user has authorized synchronized (App.class) { App.class.notifyAll(); } // send some descriptive page as a response t.sendResponseHeaders(200, 0); OutputStream os = t.getResponseBody(); InputStream is = getClass().getResourceAsStream("authorize.html"); int count; byte[] b = new byte[32000]; while (0 < (count = is.read(b))) { os.write(b, 0, count); } // close the streams os.close(); is.close(); } }); server.setExecutor(null); // creates a default executor // start the server server.start(); // open the authorization URI in the browser Desktop.getDesktop().browse(new URI(URL_AUTHORIZE + "?oauth_token=" + response.auth.token.id)); // wait until the HttpHandler sends me "notify" signal App.class.wait(); // user has authorized - stop the lightweight HTTP server server.stop(0); }
Basically, just before I send a request to the authorization URI, I start an instance of the lightweight HTTP server (present in JDK6 or you can download it here in case you are running on JDK5) which listens on http://localhost:9097/oauth, and then just wait for the callback request to come in. SmugMug, once the user authorizes, redirects the browser to this URL, which wakes up the suspended execution of my app and returns some descriptive HTML page to the user - something along the same lines as the original SmugMug page, e.g.:
Thank you for adding App to your Authorized Applications.
(You can de-authorize anytime via your control panel.)
Now, one drawback of the SmugMug approach of not allowing applications to specify the callback URL dynamically (in the request to the authorization URL) is that the port your application uses is pretty much hard-coded - you have to manually enter it (as part of the callback URL) in your API key properties on SmugMug website. That means, if the port your application wants to use is busy you have to fall back to the original approach.