Recently the RaZberry board got an update to the firmware (2.1.1 at the moment of writing) to improve security by using authentication for every HTTP request. I will not discuss the real usefulness of passing login credentials unencrypted over http instead of https, but I will show the code necessary to have it to works again.
Basic authentication
The first approach I used was to use basic authentication. Assuming I want to send this command to turn on a light:
http://192.168.0.100:8083/ZWaveAPI/Run/devices[4].instances[0].commandClasses[37].Set(255)
I added the following header:
Authorization: Basic YWRtaW46YWRtaW4=
The encoded string, is the login and password in the form login:password. In my case:
admin:admin
What I got was a nice 403 Permission Denied
Authentication with session cookie
After some research (sniffing with Fiddler :D) I saw that the webserver included in the board made a call to:
http://192.168.0.100:8083/ZAutomation/api/v1/login
passing the following Json:
{ "form":true, "login":"admin", "password":"admin", "keepme":false, "default_ui":1 }
I did not find any documentation about this specific call, but it returns the cookie with the session id; just what I needed.
Having solved the authentication problem, the calls that I have always used simply needed to pass the cookie to work as before.
Knowing that, I wrote a function that authenticate and return a CookieContainer
private CookieContainer Authenticate(string login, string password) { try { var cookie = new CookieContainer(); var request = (HttpWebRequest) WebRequest.Create(http://192.168.0.100:8083/ZAutomation/api/v1/login); request.Method = "POST"; request.CookieContainer = cookie; request.ContentType = "application/json;charset=utf-8"; var encoding = new UTF8Encoding(); var postBytes = encoding.GetBytes(string.Format("{{\"form\":true,\"login\":\"{0}\",\"password\":\"{1}\",\"keepme\":false,\"default_ui\":1}}", login, password)); request.ContentLength = postBytes.Length; using (var stream = request.GetRequestStream()) stream.Write(postBytes, 0, postBytes.Length); request.GetResponse(); return cookie; } catch (Exception e) { Log.Error(m => m("Error authenticating to url: {0}", urlLogin), e); throw; } }
From here, the edit to my existing calls was trivial. I just needed to add the cookie like this:
private void SendCommand(Device device, Command command) { var value = string.Format(urlCommand, device.Id, device.Params[ParamInstance], device.Params[ParamCommandClasses], command == Command.Off ? device.Params[ParamOff] : device.Params[ParamOn]); try { //Get authentication cookie var cookieContainer = Authenticate(authLogin, authPassword); if (cookieContainer == null) { Log.Error(x => x("Error authenticating to RaZberry")); return; } //Send the command with session cookie Log.Debug(x => x("Sending command to url: {0}", value)); var request = (HttpWebRequest)WebRequest.Create(value); //here is our session cookie request.CookieContainer = cookieContainer; request.Method = "POST"; //We have to close the response or .Net will stop sending requests when it reach the maximum number allowed. //That's due to the Keep-alive setting that is On by default. //In addition, it is non blocking request.GetResponseAsync().ContinueWith(x => x.Result.Close()); device.UpdateLastCommand(command); } catch (Exception e) { Log.Error(m => m("Error sending command to url: {0}", value), e); throw; } }
Now everything works, as it should. The only issue is that I have to make two calls every time I need to interact with my devices and I do not like it. Anyway, it seems that from version 2.1.2-rc0, they added basic authentication support, so in the next weeks we should be able made just one call, like in the first attempt I wrote about, at the beginning of this post.