openLuup: Home Assistant Bearer Authorization

I can’t see anything in openLuup which would hinder this. How do you specify the token? Can you PM me an example that works on Vera?

Here’s a link to the API: HA API You basically generate your own user token through the HA UI, and then use the token according to the HA API syntax.

I have not been able to get authorization working on openLuup. My header syntax is:

r, c, h = http.request { url = theURL, method = 'POST', headers = { ['Content-Type'] = 'application/json', ['Content-Length'] = string.len(request_body), ['Authorization : Bearer '] = HomeAssistantToken }, source = ltn12.source.string(request_body), sink = ltn12.sink.table(response_body) }

I have also tried ['Authorization'] = 'Bearer '.. HomeAssistantToken but this did not work either. However, the first method above does return a 401 error, telling me that the API is seeing the request, but is not accepting the token, while the second example syntax returns a 404 error.

This problem has nothing to do with openLuup because you are using the LuaSocket library directly. The openLuup HTTP client module doesn’t even see the request or the reponse.

Your first header syntax version is totally invalid, and the HA API will not see an authorization header at all. Hence the 401 error.

The second version should be valid, as long as the token string is correct. Looking at the documentation you pointed me towards…

Successful calls will return status code 200 or 201. Other status codes that can return are:

400 (Bad Request)
401 (Unauthorized)
404 (Not Found)
405 (Method not allowed)

I see that a 404 error is a possible return from the API, so it does not necessarily mean that the request has not been received, but that a resource is not found. Perhaps this also suggests a problem with the token.

Without the surrounding code to the http.request() call, it’s hard to know what is going on. But are you saying that one version of this actually works on Vera?

I have had HA integrated with openluup for quite some time either through the site sensor plugin or through luup code as you are doing now. I bypassed the authentication by setting the openluup ip as part of the trusted network but initially used the legacy authentication. The more recent token have not been much of a problem either. As akbooer posted your second syntax looks more correct than the first. You may also have another problem with the HA entity you are trying to poll.

1 Like

From what I can tell from researching the issue, the problem is that luasocket does not support authorization bearer: only basic and digest authentication.

As well, openLuup does not support bearer authentication. If this is incorrect, please display the proper syntax.

So I don’t know how you were able to use tokens with an http call. I have been able to do what I need by shelling out to an OS curl call, but I’m not real happy with doing that. I suppose I could spend another day or two trying libcurl, or openresty, both of which seem to support modern authentication, but there is only so much time in lifetime to invest in unraveling the undocumented features of this platform.

I could be wrong but the bearer authentication token is only a string in the header. There is nothing in openluup which does not support it. It is no different than the legacy password. Your output errors indicates that you actually passed the authentication and that your call is not finding the variable or entity you are seeking. I have been making lua calls from my openluup to my home assistant api extensively as I synced up roomba, xiaomi robots, notification etc… never had any problem. Alternatively as I suggested, you could set openluup within your trusted network and not have to deal with the authentication mess.

I can confirm after testing it on my setup. I created a long lived token on my Hass and plugged it into the header construct of a post function I created in the startup lua of openluup and it works. I used it to send notifications to pushover.
This is the postH function I use

function POSTH(path, payload)
    local http = require("socket.http")
    local ltn12 = require("ltn12")
    local resp = {}
    local resp, code, resp_headers, status = http.request
    {
    url = path,
    method = "POST",
    headers =
        {["Authorization"]= "Bearer ABCDEFGH",
        ["Content-Type"] = "application/json",
        ["Content-Length"] = payload:len()
        },
    source = ltn12.source.string(payload),
    sink = ltn12.sink.table(resp)
    }
end

Obviously replace ABCDEFGH with your long lived token.

I then use this function in various plugins and scenes in openLuup. where I set the url to my home assistant API and the payload to what it needs to be.

to further illustrate how it works this is my function to send tts to my amazon echos. I then just use alexatts(“hello”) in my scenes.

function alexatts(mess)
local json = require "dkjson"
local payl = {
    message = mess
    } 
local payload = json.encode (payl)    
local path = "http://hassip:8123/api/services/notify/all_alexas"
POSTH(path, payload)
end
1 Like

This is exactly my understanding.

My earlier assertion (in reply to issue on GitHub) that this was not supported by openLuup was in the context of the luup.inet.wget() function, which, indeed, only supports Basic and Digest authentication when provided with a Username and Password.

Not meaning to pile on but… as I researched this a little further, the token authorization mode is actually the simplest of them (vs basic and digest) since unlike these two, doesn’t have any encoding and just passes on a token string. basic encodes a username and password with mime64 and digest adds an MD5 crypto to it. The bearer token requires no additional support and is the least secure of the three.

As with all these methods, it should really be used over HTTPS only, but internally, on a LAN, it’s hardly a problem.

Exactly! and home assistant has an additional configuration item allowing you to set a trusted network allowing you to set an ip range or an ip as trusted and therefore bypass the authentication process. I did not set my entire LAN as trusted, only a few specific devices.

Thanks @rafale77. If you were able to do this, then I’ll keep at it until I figure out what is causing the call to fail. BTW, I’m doing more or less the same thing as you’ve demonstrated.

The documentation is awful on Lua Bearer A. There are several stack overflow topics with no apparent resolution see: Bearer Authentication

As well, the socket library leads one to believe that this format is not incorporated into the module: Authentication as it specifies only basic and digest.

But if you made it work, then it can be done… My code works with a curl call, so all the pieces are correct. It may be a problem with the way the JSON payload is formatted as my experience is that the slightest deviation from the proper syntax (even whitespace) can cause a JSON call to fail. Here’s the bulk of the code:

local request_body = json.encode {message = myEchoMessage, data = {type = "announce",method = "all"}}
local response_body = {}

local theURL = 'http://'..HomeAssistantIP..':'..HomeAssistantPort..'/api/services/notify/'..myEchoDevice  

L("URL(%1)", theURL)
L("Request Body:(%1)",request_body)
L("Request Length:(%1)",request_body:len())

r, c, h = http.request {
      url = theURL,
      method = "POST",
      headers = {
	["Content-Type"]   = "application/json",
	["Authorization"] = "Bearer ".. HomeAssistantToken,
        ["Content-Length"] = request_body:len()
      },
      source = ltn12.source.string(request_body),
      sink   = ltn12.sink.table(response_body)
}

The variables in the call are passed in from state variables defined in the I_xls file. The variables print accurately, and are in scope. It may be the use of the word “Type” as a field definition in the JSON as this is a protected lua word. Unfortunately, that is the word that HA decided to use in their data model.

Hmm maybe I am crazy here but have you updated the luasec and luasocket libraries by any chance? It is also very possible that for various reasons (mostly trying to write/modify some plugins) I have updated a bunch of lua libraries over time. Also keep in mind the trusted network feature of Hass.

The changes in its authentication going from the legacy login X-Access to authentication bearer, at around 0.89 got me a bit frustrated and got me to use this feature instead.

‘type’ is usually not a problem in this context, although ‘function’, for example, is a problem. You can always use the alternative syntax in these cases…

data = {["type"] = "announce",method = "all"}

Formatting of the result may be an issue, although a proper JSON parser shouldn’t worry about this. [Edit: Perhaps you need to url encode the JSON string? …obviously not, since this is posted content!] You could always log it to check the format, or remove control characters with:

body = body: gsub ("%c","")

OK, the alternate syntax works.

local request_body = json.encode {message = myEchoMessage, data = {[“type”] = “announce”,method = “all”}}

So the plugin runs and the bearer token is working. Thanks guys.