External Web page with Javascript to send command to Items

Hi there,

I’m trying to send command to my OH3 Items via an external web page with some Javascript code.

So far I’m successfully getting item status but I’m struggling with command actions (ON & OFF).

The script I’m trying to use is the following:

function command() {
      const xhr = new XMLHttpRequest();

      xhr.open("POST", "https://IP/rest/items/someItem");


      xhr.setRequestHeader("Content-Type", "text/plain");
      xhr.setRequestHeader("accept", "*/*");
      xhr.setRequestHeader("Authorization", "Bearer SomeBearer");
      

      xhr.send("ON");  
    }

What I’ve tried:

  1. I’ve put some Bearer as I found from my OH3 admin tab, Developer Tools/Api Explorer but I got a 401 error - Authorization response from server
xhr.setRequestHeader("Authorization", "Bearer SomeBearer");

  1. I thought I need some api token to send with the request. So I created one from the admin tab, activated the Basic Auth from Settings/API protection and tried to send the request this way:
xhr.setRequestHeader("Authorization", "Basic oh.APIs.someToken");

I was not successful…

  1. Changing the header this way:
xhr.setRequestHeader("Authorization", "Bearer oh.APIs.someToken");

I got a 405 Error - Method not allowed

  1. Tryed to use alternative headers like below
xhr.setRequestHeader("Authorization", "Basic" + btoa("oh.APIs.someToken));

and

xhr.setRequestHeader("Authorization", "Basic" + btoa("admin" + ":" + "myAdminPassword"));

and

xhr.setRequestHeader("Authorization", "Basic" + btoa("admin:myAdminPassword"));

and

xhr.setRequestHeader("Authorization", "Bearer myToken");
      xhr.setRequestHeader("WWW-Authenticate", "Basic");

still no luck…

Do you have any suggestion on how to make this request working?

Hi there,

This JS works for me within my LAN.

function onTest() {

var url = 'http://192.168.1.163:8080/rest/items/AllOffLockOut';

var xhr = new XMLHttpRequest();

xhr.open("POST", url);

xhr.setRequestHeader("Content-Type", "text/plain");

var data = "ON";

xhr.send(data);

and for a GET request,

//Get request
var request = new XMLHttpRequest()
request.open('GET', 'http://192.168.1.163:/rest/items/AllOffLockOut', true)
request.onload = function () {
  var data = (this.response);   
  console.log(data);           // Returns a string (not very useful to work with)
  console.log(data.length);    // Can see the length of the string 
  console.log(typeof data);    // Identifies as a string type 
  var dataJSON = JSON.parse(data);      //parses the data to an Object
  console.log(dataJSON);                
  console.log(typeof dataJSON);         // Object
  console.log(dataJSON.state);          // Just calls out the state value!!!
  console.log('The item is called: ' + dataJSON.label );  
  console.log('With a state of: ' + dataJSON.state );    
}
request.send()

Thank you for helping, I had no luck this time neither…

I’ve tried to bypass the reverse proxy, uncheck Basic Auth from API settings and modify the code in order to point directly to my OH3 server but received back a 405 error: Method not allowed.

Maybe the issue is somehow related to my configuration… let me explain.

My setup is the following:

1 – OH3 server

2 – Nginx Proxy Server (just to handle https connection and redirect them to internal OH3 server in http)

3 – Nginx web server hosting my html with the code I’m having problem with

I’ll hereunder post my Nginx servers config, maybe somebody will found some stupid mistake I made:

Nginx server hosting my HTML and JS code:

server {
        listen 80 default_server;
        listen [::]:80 default_server;
        listen 443 ssl default_server;
        listen [::]:443 ssl default_server;
        ssl_certificate (path to crt);
        ssl_certificate_key (path to key);
        

        root /var/www/html;
location / {
    
    if ($request_method ~* "(GET|POST)") {
      add_header "Access-Control-Allow-Origin"  *;
      add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Ty>
    }

    # Preflighted requests
    if ($request_method = OPTIONS ) {
      add_header "Access-Control-Allow-Origin"  *;
      add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD";
      add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type
    }

Nginx Reverse proxy:

server {
    listen 80;
    return 301 https://$host$request_uri;
}

server {

    listen 443 ssl;
    server_name proxy;

    ssl_certificate           (path to crt);
    ssl_certificate_key       (path to key);

#    ssl on;
    ssl_session_cache  builtin:1000  shared:SSL:10m;
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
    ssl_prefer_server_ciphers on;

    access_log            (path to log);


    location / {
    add_header 'Access-Control-Allow-Origin' '*' always;
    add_header 'Access-Control-Allow-Headers' '*' always;
    add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

      proxy_set_header        Host $host;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        X-Forwarded-Proto $scheme;

      proxy_pass         ip to OH3 server
      proxy_read_timeout  90;
      proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    }
  }

Any ideas?

By the way, sending a curl command from my Mac terminal I can successfully send ON&OFF commands to the item:

curl -X POST "http://OH3serverIP/rest/items/itemToSwitchOn" -H  "accept: */*" -H  "Content-Type: text/plain" -H  "Authorization: Bearer someAuthBearerCode" -d "ON"

Same result when sending curl -k to OH3 via proxy in https (need to add -k because the ssl certs are self signed)

I really have no idea on what’s happening here…

Just tried to switch from Safari to Chrome, I got a quite similar error message…

[Deprecation] "Authorization" will not be covered by the wildcard symbol (*)in CORS "Access-Control-Allow-Headers" handling.

POST https://OH3serverIP/rest/items/ItemsoControl/state 401 (Unauthorized)

hum… :thinking:

Hi,
i wrote a private webpage using react and fetch to show and toggle items. There is no auth required to toggle items (send commands).

I solved the CORS problem by providing the OH rest api as special /rest endpoint.

The following files are your static webpage:

  • /index.html is your web page
  • /js/main.js is an example for your script

And your reverse proxy is

CORS is a security feature to prevent requests to other domains. Domains are Google.com, 192.168.0.3:8080 or localhost:3000. This part of the url has to be the same. It is also possible to configure Cross origin requests but mapping the OH server as /rest/ is easier.

Hope that helps to get your project running

PS: Cors can be disabled in chrome by using chrome.exe –disable-web-security. I use this for local testing (calling localhost:8080 from dev server localhost:3000). But it is really just an option for testing/debugging

Ok so you’re suggesting to add the following code to my Proxy conf file?

location /rest {
    add_header 'Access-Control-Allow-Origin' '*' always;
    add_header 'Access-Control-Allow-Headers' '*' always;
    add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

      proxy_set_header        Host $host;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        X-Forwarded-Proto $scheme;

      proxy_pass         ip to OH3 server
      proxy_read_timeout  90;
      proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    }

I am not using nginx, thats why i did not past an example in my post… but yes!
There is only one pitfall, you should proxy directly to oh/rest. Assume your OH IP is 192.168.0.3, you should use proxy_pass 192.168.0 3/rest.

To make it less confusing my setup uses location /api mapped to 192.168.0 3/rest. In this setup it is clear that all requests to /api have to go through the proxy.

Thank you JanK for suggesting this mod,

adding the path /rest into my Proxy conf file makes the CORS warning disappear.

However, I’m still getting the 401 Error - Unauthorized whet attempting to send commands (ON or OFF) to OH3 via Javascript

Nice, I allways assumed the rest api is not protected. Probably I just missed this feature in the release notes :smiley:

There is a setting http://openhab-url/settings/services/org.openhab.restauth to secure the REST API. Now I get only 401 like you. I will activate this feature in the future and can tell you how to do it in a week or two.
But till then: openHAB API tokens | openHAB

Thanks again for your patience,

I’ve already tried to generate such API tokens and send them via the JS script: the error still remains:

Both of them generate the error here below:

Maybe I don’t implement the xhr.setRequestHeader correctly?

Works :smiley:

Create an API token as descirbed in the docs and use it in your fetch command like this:

    const response = await fetch(url, {
        headers: {
            Authorization: "Bearer " + TOKEN,
        },
        method: "POST",
        body: command,
    });

Now it works and is a security nightmare. Now your key is part of the javascript source code and everyone using your page has the key. As a local intranet page, this works fine. Do not use your code on a internet page.

Well that is a different error. 405 instead of 401.

You can POST to rest/items/someItem or PUT to rest/items/someItem/state.

Try the (swagger) documentation under: openhab-url/developer/api-explorer. There you can test your URLs and as I just figured out, you also see the CURL code including the Bearer token.

1 Like

Thank you so much JanK,

you helped me resolving the issue that was driving me crazy :rofl:

I tryed to add the following code to my Nginx proxy conf file and then realized I should have implemented it in my Nginx web server as well.

location /rest {
    add_header 'Access-Control-Allow-Origin' '*' always;
    add_header 'Access-Control-Allow-Headers' '*' always;
    add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';

      proxy_set_header        Host $host;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        X-Forwarded-Proto $scheme;

      proxy_pass         ip to OH3 server
      proxy_read_timeout  90;
      proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    }

That solved the issue, so thanks again, I’m now able to send the requests via https through the Nginx proxy to my OH3 server

Just two more (stupid) question:

  1. I’ve read somewhere that the bearer code OH3 provides in Api Explorer won’t last too long, so it would be better to use the API (as you previously suggested). Is that right?

  2. security issues: the source code of my web page contains the bearer code, so everyone could send request to my OH3. since my intention is to build a PWA APP to share with my family members, which will send requests and commands to my home OH3 via the reverse proxy, do you think I shall prefer other ways of securing the communication between server and clients in order to avoid security issues?

I have to confirm what I said in point 1 of the last post: the bearer code acquired from the Api Explorer only lasts few minutes.

So, how can I handle this now?
Using the API oh.callapi.XXXXXXX instead of the bearer code actually works and should last longer.

Hi @lucaset256,

Great that we could solve the first problem. Now it is more about architecture of your application. As I told you, the authentication is new to my and my app is currently a local one. Security is here not important, if my neighbor hacks the wifi, my lights are a negligible problem.

Back to your task. I do not know what the short living token is, probably something from the auth rest endpoint. This does not solve the problem with the token inside your browser is code.

Solutions

Use myopenhab-cloud instead of your own app. It is solution zero because you want your own app… to include a blog and photos as well :wink:

I think the easiest way in terms of less self written and managed infrastructure is myopenhab-cloud. I am not hundred percent sure but perhaps they allow CORS and your PWA could request data from there. They have a user management and you can provide username and password to your family members.

Smartify your reverse proxy. I use Caddy instead of nginx. There is a fancy Caddy plugin that implements a user managment. Caddy can protect some routes with a token. You do not need the OH token setting but protect your Luca.com/rest with a token.

I previously always said browser code. If the OH token is inside your server it is fine. A nodejs Express app can be your smart router. But then the authentication is moved from OH to nodejs.

Explaining all this makes clear I do not know what the design of OH suggests for this application type. The android app needs a token but that is stored in the secure key manager of android. Your browser does not has something like that.

I am planning to use my own nodejs application but I probably integrate other apps as well. I am curious how to handle the event stream then. With my local app I was able to get live updates

well, I currently have an active openhab APP via their cloud but I’d prefer to design my own one just because I find the original not so much customizable.

I started coding a native app with Xcode for my iPhone, then realized our family members actually use both iPhones and android, so a PWA app would be the best option.
Let me show you what I’m currently designing:

Data requests from app to OH3 server or OHCloud
This was one of my biggest uncertainty. I obviously need to request data and states, and I’m still not sure how to handle that: whether to go directly to my home OH3 server or not.
Requesting data to openhabCloud would be really interesting (option 1)… I’m trying to get some more information on that, do you know where I can take a look at?

Google is your friend and the community… they have answers: