Fuddled API, Verbose Workaround
I've started writing some Scala applications (including one atop the Lift web framework) to access Unfuddle's API recently. I've mainly been building daily burndown reports for my team at Treehouse Agency. I've run into a few issues with API methods not working as advertised, and Unfuddle's been pretty good about fixing most of them.
The problem I've been experiencing as of January 5th is that Unfuddle has subtly broken authentication for client libraries that (wisely) wait for a 401 error with an accompanying WWW-Authenticate: Basic header before sending credentials. (Namely, Unfuddle's API stopped sending a WWW-Authenticate header altogether.) If need be, you can force most HTTP client libraries to send authentication on every request in one way or another, and that's what I had to do tonight with the excellent Databinder Dispatch library.
The code to prepare a request that will send Basic credentials when the server requests them is concise:
import dispatch._
val req = :/("%s.unfuddle.com" format subdomain) / "api/v1" as (user, password)
Clean and simple, right?
To force an outgoing HTTP basic auth header with the least fuss, you'll need to create it by hand by Base64 encoding a user's username and password pair. The Apache Commons library includes a Base64 encoder and decoder, so by dipping your toes into Java-land you can construct this header. Once the Base64 value is encoded, the <:< method of the dispatch.Request class accepts a Map[String, String] and will set the corresponding headers on the outgoing web request.
Here's the full workaround I came up with.
import dispatch._
import org.apache.commons.codec.binary.Base64
val authorizationString = "Basic " + new String(Base64.encodeBase64("%s:%s".format(user, password).getBytes))
val req = :/("%s.unfuddle.com" format subdomain) / "api/v1" <:< Map("Authorization" -> authorizationString)
So there you have it - a method to construct a Databinder Dispatch web request that always sends an HTTP basic auth header. Hopefully, all the web APIs that you interact with will do the right thing regarding WWW-Authenticate headers, but if not, now you'll know how to cope.
UPDATE: In case you're wondering if this is happening with an API that you're interacting with, the specific error that indicates that Apache HttpClient (the underlying workhorse of Databinder Dispatch) did not receive any WWW-Authenticate headers looks like this:
WARN - Authentication error: Unable to respond to any of these challenges: {}
(not verified) Jan 09, 2010
Maybe I should just implement it this way in Dispatch; HttpClient's CredentialsProvider interface was not very amenable to per-request configuration and it would be nice to get rid of it entirely. I could just say in the docs that Request#as is a shortcut for blindly putting a name and password in the header, so be sure to get the host name right. :\
Well, hopefully we will see less and less APIs using Basic auth and the user's normal username and password, as Unfuddle does. The irony is that things worked fine for several weeks as I was developing the app, and then Unfuddle stopped sending WWW-Authenticate headers for whatever reason.
(I was thinking about doing a wide release of this tool once I had perfected it for our use, but I'd have to store a plaintext user and password for anyone who wanted to use it, which is suboptimal.)
In any case, I love how elegant the current syntax is, and with most of the APIs I've used, it just works. Maybe one option would be to overload the as method to take a third Boolean parameter to always send the auth header? The only other solutions I can think of would involve method names far more verbose than as, along the lines of "alwaysAuthenticateAs" or "forceAuthenticationAs".
(not verified) Jan 10, 2010
Thanks for the tip. I ran into the same issue
(not verified) Jan 13, 2010
Maybe
http as_! (name, pass) ...
I think I'll add that in for 0.6.7, deprecate the existing "as" in 0.7, and take it out in 0.8. Most people are using Dispatch with Web APIs, and if anybody wants to use HttpClient's CredentialsProvider as it's indented to be used they can do that in an Http subclass.
Luckily, I no longer need my workaround. Unfuddle fixed their broken behavior: http://unfuddle.com/community/forums/3/topics/816?page=1#posts-2316 .
I think as_! could be a nice option for the next version.
Post your comment