RaZberry and HTTP api with authentication in C#

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.