[Unsupported] Homey v2 REST API

That means that you weren’t quick enough :wink: The code that you cut-and-paste from the browser has a very short expiry time.

1 Like

Looks good to me. Weird.

Got it working now… :grinning:

1 Like

Here’s how to have your application login to homey with just your e-mail address and password (once) and obtain a refresh token for subsequent access.
Notice, the refresh token will expire, either by expiration or revocation.
There are quite a few more things to discover, but this will get you going.

In the following steps, I didn’t register a new application (probably possible, but I didn’t look into that yet), I just borrowed the client_id and client_secret from developer.athom.com (client_id=598d85a330e1bb0c0d75b8eb and client_secret=ba93fc861b204732607169fb29c2708f1da7e17f). Other combinations for client_id and client_secret will also work (for example homey.ink), as long as the redirect_uri matches.

Step 1, get a JWT with your credentials.
HTTPS-POST your e-mail address and password, form-url-encoded, to accounts.athom.com/login.
No client_* needed at this time.
The JSON reply is the JWT as {“token”: “jwt”}

Step 2, obtain a delegation code.
HTTPS-GET accounts.athom.com//authorise?client_id=xxx&redirect_uri=uri&response_type=code&user_token=JWT from previous step
Since I used the client_id from developer.athom.com, the uri in this case is the aforementioned. The redirect_uri is important and will be checked (as it should be)
Be aware, the HTTP result will be a 302 (redirect).
The location-header contains a query parameter ‘code’. It’s value is the delegation code. The header looks like: “location: https://redirect_uri?code=89bc40ceXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX29”.

Step 3, get an access token
HTTPS-POST api.athom.com/oauth2/token, form-url-encoded
Body: client_id=XXX&client_secret=YYY&grant_type=authorization_code&code=Delegation code from previous step
The JSON result looks like this in readable format:
{
“access_token”: “51163d8xxxxxxxxxxxxxxxx8282579f8fe77ee”,
“expires_in”: 3660,
“refresh_token”: “f7d635axxxxxxxxxxxxxxxx1c2a258f4e33d15b”,
“token_type”: “bearer”
}
Use the access_token for the next step, but also keep the refresh_token. You will need the refresh_token in the future to obtain a new bearer code.

Step 4, obtain a JWT to login to your homey
HTTPS-POST api.athom.com//delegation/token?audience=homey
Add an http-header: Authorization: Bearer access_token from previous step or step 6
The JSON result contains just the JWT.

Step 5, login to homey
HTTPS-POST uri/api/manager/users/login
Body: {"token”:”JWT from step 4”}
The reply is the desperately sought bearer token in JSON format.
uri can be a number of things:

  • The fastest option is homey’s local IP address.
  • The second is a local qualified DNS name.
  • Another option is: w-x-y-z.homey.homeylocal.com (w-z = the local IP address bytes).
  • More options exist, all through the cloud and pretty much equally slow.

The bearer code will expire after 24 hours. To obtain a new one without user credentials, see step 6.

Step 6, refresh the bearer token
The bearer token expires every 24 hours and with good reason.
If you saved the refresh_token from step 3, you can obtain a new access_token and hence a new bearer token. Note: this will not go on indefinitely. At some point you will have to login again.
HTTPS-POST api.athom.com/delegation/token?audience=homey, form-url-encoded.
Body: client_id=XXX&client_secret=YYY&grant_type=refresh_token&refresh_token=<refresh_token>
The response is similar to the response in step 3. Use the access_token to repeat steps 4 and 5. Keep the refresh_token for the next refresh (The refresh_token may change).

With the access_token received in step 3 or 6 you can find useful information like cloud id and homey’s ip address:
HTTPS_GET api.athom.com/user/me
Add header: Authorization: Bearer <access_token from step 3 or 6>

1 Like

Lammy I have a problem with step 2. Step 1 is working. In step 2 the page that is returned is not the page (302) you describe with a location header. When i use client_id and redirect_uri from homey.ink. It is the page where I should allow the requested grants. No location header here.

Hmm, I see @Rom. You need an extra step between 2 and 3, let’s split step 2 in 2a and 2b. Step 2a is the same as the former step 2 from your side, but with a different reply (http 200 iso 302). The extra step 2b is to mimic the form returned in step 2a, as if you pressed the Allow button, i.e. post the same parameters using form-url-encoding.
Something like this: _csrf=xxxxxx&resource=resource.homey.yyyyyyyy&allow=Allow
All the parameters are in the returned html from step 2a.
The seasurf (_csrf) is a bit puzzling to me at the moment, because it is in the returned html (at least that’s how it appears in my browser’s console) and it escapes me how this should prevent forgery. Another puzzling thing is that step 2a returns a csrf cookie with a different value. You might need to add the cookie from 2a to the headers for 2b, I can’t tell yet.
My code is running on an embedded system with a self-built https client that doesn’t support chunked compressed bodies yet, so I can’t test it at the moment. I hope to find some time next week to extend my https client and/or play around with curl and see what I can find out.
By the way, if you split the jwt from step 1 by the dots(.) and b64decode the middle part, you’ll get the id of the homey that you’ve logged in to (The y’s in resource=resource.homey.yyyyyyyy).
Hopefully this gets you going.

@Lammy. Thank you for your research so far. I will try your suggestions next weekend.

Hi I have tried this command, but it’s appear it’s wrong ! How can I update a swith from on to off or off to on with http link?

http://<HOMEY_IP>/api/manager/devices/device/ff469433-66b3-458d-ad73-14530d8f9b39/capability/onoff

regards

Does this solution work only on localhost or other hosts as well? This is most related to the redirect URL, which is not editable in Developer portal and is set to http://localhost, http://localhost/oauth2/callback, because Athom does not provide authentication in production yet.

Your url is correct. Use the PUT method, put this in the body: {“value”:false} and send this header “Content-Type: application/json” along. Use true to switch on and false to switch off.

Localhost doesn’t work. Localhost refers to the machine your own code is running on, but you want to connect to Athom’s api host. Use api.athom.com instead.
Note: localhost works only on Athom’s server, if localhost resolves to an address for the same machine as api.athom.com.

I got that, but in Step 2 you need to set query param redirect_uri, together with the client_id. Both values are found at Athom Developer Portal. redirect_uri is localhost in Developer Portal. This works when I test your steps locally on my machine, but if I deploy my code to for instance AWS, I guess redirect_uri cant be localhost

The redirect_uri targets (interactive) browsers, but this method performs authentication programmatically. Even if your code would get a redirect to localhost, you can ignore it and extract the token.

1 Like

So I can just set the redirect_uri to localhost, according to Athom Developer Portal. Since web browser will not follow/catch the redirect anyway

The browser will catch it. The method described is meant to be used from server-side code.

You can try following the steps using a CLI client like cURL to see how it works.

Aha, now I got it! I was thinking that these steps should be performed server-side, otherwise redirects will be caught by the browser. Thanks for the clarification!

Im just sad to see all these creative workarounds to get the authentication working seamlessly. If Athom just provided OAuth / OIDC authentication flow, this should be unnecessary

2 Likes

I agree. It was promised a year or so ago, when Homeydash development started, but never materialized.

1 Like

Just noticed I didn’t mention the http headers.
These are the ones I use in all calls:

  • “Connection: keep-alive”
  • “Accept-Language: en-us”
  • “Accept-Encoding: br, gzip, deflate”
  • “Origin: anyname://authorize”
  • “Referer: anyname://authorize”
  • “User-Agent: Anyname/1.0”
    Add this header if you post form data: “Content-Type: application/x-www-form-urlencoded”
    And this header if you send JSON in the body: Content-Type: application/json
    Notice, I used a fake application url scheme for Origin and Referer.
1 Like

In what context do you use this authentication method, @Lammy? CLI/Web Page/Server Side API? If you are using JavaScript, could you share your code with us/me?

It is C/C++ code, running on armgnulinux. Too much to post it all here, but here is for example the function for step 1 (indentation is lost).

athomLogin

bool athomLogin(std::string email, std::string passw, std::string &jwt)
{
const char *headers =
“Accept: application/json\r\n”
“Accept-Language: en-us\r\n”
“Connection: keep-alive\r\n”
“Accept-Encoding: br, gzip, deflate\r\n”
“Origin: domos://authorize\r\n”
“Referer: domos://authorize\r\n”
“Content-Type: application/x-www-form-urlencoded\r\n”
“”;
bool res = false;
size_t sz = 512;
char buffer[sz], payload[sz];
https_handle_t hnd = httpsCreateHandle(“accounts.athom.com”, 443, headers);
sprintf(payload, “email=%s&password=%s”, urlencode(email).c_str(), urlencode(passw).c_str());
headers_t ret_headers;
if (200 == https(hnd, “”, “POST”, “/login”, payload, strlen(payload), buffer, &sz, &ret_headers))
{
//for (size_t i = 0; i < ret_headers.size(); i++)
//{
// printf("%s\n", ret_headers[i].c_str());
//}
json reply = json::parse(buffer);
//printf("%s\n", reply.dump(2).c_str());
jwt = reply[“token”];
res = true;
}
httpsDestroyHandle(hnd);
return res;
}

3 Likes