Recently, I was trying to add HTTP digest authentication on my Home automation device. The device exposes a REST interface trough a proxy server. My web server is setup like this
Now since my API is exposed to the world by proxying it like that, I wanted to add security by implementing HTTP digest authentication. Whether or not Digest authentication with MD5 is secure or not is a completely different story, but let's assume it is good enough for now. I have a restricted access webpage that I go on to control my home automation device. This web page makes requests to DHAS using javascript. Since I've implemented digest authentication, I now need to put the credentials in the javascript so that the calls made with XMLHttpRequest can succeed. Even though that javascript code will only be served to me, while I am authenticated on the website, I felt uncomfortable to leave a hardcoded username and password in the JS source. So this is what I came up with:
Note that messages sent from JS to DHAS are being proxied by Apache. Therefore, DHAS receives a GET for /insteon/listmodules and not for /dhas/insteon/listmodules
- use XMLHttpRequest to make a request to DHAS (through the proxy)
- add a header "X-NeedAuthenticationHack" in the request
- receive a 401
- get the "X-WWW-Authenticate" header from the 401 response
- Make a XMLHttpRequest to the server and send it the "X-WWW-Authenticate" data
- Server side php script with hardcoded username/password for DHAS solves the challenge and returns the resonse
- use XMLHttpRequest to make a request to DHAS (through the proxy) and append the response in a "Authorization" header
So basically, I just intercepted the 401 and instead of letting the browser prompt for a username password, I created the response myself. And instead of doing in the JS, I did it on the server, limiting the exposition of the username/password. You may notice my two special X headers. This is because if the server returns a 401 with WWW-Authenticate, the browser will prompt for your credentials. Event if I have a handler defined to get the 401. So when I send my initial request, I set the X-NeedAuthenticationHack header to tell the server: "Hey, don't send me a WWW-Authenticate, send a X-WWW-Authenticate instead so I can deal with it".
By the way, even if the information is easy to find, this is how the digest authentication is done:
- Client makes request to http://webserver.com/url1/index.html
- Server sends a "WWW-Authenticate: realm="testrealm", nonce="testnonce"
- ha1 = md5("username:testrealm:password")
- ha2 = md5("GET:/url1/index.html")
- ha3 = md5(ha1+":testnonce:"+ha2)
- Client sends: "Authorization: Digest username="username", realm="testrealm", nonce="testnonce", response=""+ha3+"", uri="/url1/index.html"