403 on device subscription

Another day, another mystery working with SmartThings. I’m getting an inexplicable 403 Forbidden error when attempting to subscribe to device updates:

POST https://api.smartthings.com/installedapps/<installation>/subscriptions
Authorization: Bearer <personal-token>

{
  "sourceType": "DEVICE",
  "device": {
    "deviceId": "[redacted]",
    "componentId": "main",
    "capability": "*",
    "attribute": "*",
    "value": "*"
  }
}

Response: 403 Forbidden (blank body, no error message).

  1. I have the correct permission (from the install callback): r:devices:[device-id], w:devices:[device-id], x:devices:[device-id]
  2. The personal token is correct (can make calls to the API no problem).
  3. The installed app ID is correct (from callback)
  4. The device ID is correct (checked with API call)

Nothing in the “live logging” console. I’m doing this all through calls in Insomnia and Postman after the install process, on-demand.

The docs say:

" SmartApps may create subscriptions for specific devices selected and authorized by the user during Configuration."

Do they HAVE to be created only during the install process only? Is there any reason the auth_token is a duration of 5mins?

UPDATE: this actually appears on ALL verbs for subscriptions. a GET /installedapps/{id}/subscriptions with a personal access token results in a 403. Is this intended? It seems odd, to say the least.

Hello @Alex_Cstr8,

The Subscription’s request must use the token generated by the SmartApp because the personal token is used to interact with the API for non-SmartApp use cases (the Authorization and permission document shows the scope of each token type).

Also, you can configure the subscriptions along with the app creation; here is an example to create a simple SmartApp where Subscriptions are used, or, you can visit the SmartThings GitHub to check other samples according to what you need.

I hope this is useful to you. Let me know if you have any questions.

Best regards,
Nayely Z.

Thanks @nayelyz! We solved it a while back now (with considerable frustration and pain), and my general view on it is the design is frankly, horrible. It would be considerably easier if the access token had a more conventional lifetime, as it tends to in other OAuth2 applications. It makes no sense at all; neither does not being able to subscribe to your own devices’ telemetry data.

I don’t think the subscriptions should be confined to to SmartApp token. Conceptually it’s no different from a schedule (which can be accessed from a Personal Token). One is triggered by a cron event. The other is triggered by a device state. But they are both within the context of an installedApp. The PAT can be granted access to read the devices, so why can’t it just do it on a notification basis, rather than a polling basis?

The doc https://smartthings.developer.samsung.com/docs/api-ref/st-api.html#operation/listSubscriptions says nothing about the difference.

I understand that’s how it currently works. I don’t think it’s conceptually correct. Here the PAT isn’t actually being prevented from making the desired access, it’s just being forced to go a roundabout way to get it. Permissions are about allow/not allowed, not the number of hurdles you need to go through.

Hello @wtsang01,

The token provides security, this means that, the subscriptions created with the SmartApp cannot be modified from another source, because the token of the SmartApp is derived from the permissions granted by the user and has a specific validity period. On the contrary, the Personal Access Token is valid for fifty years from the time of its creation.

You can refer to the Authentication part of the API documentation.

I’m not sure if you have responded to my point.

According to Scopes, for each type of scope, and each type of token, it is either applicable or not. And if it is applicable, the PAT is applicable to the account that owns it, and the App token is applicable to the SmartApp’s location (sic, should it really mean installation). That’s all it says. There’s no “yes, but let’s look for some arbitrary exceptions”

So if we now focus on subscriptions:, this is what the documentation says:

“r:devices:* - Read details about a device, including device attribute state. For SmartApp tokens, the scope is restricted to the location the SmartApp is installed into. For personal access tokens, the scope is limited to the account associated with the token. This scope is required to create subscriptions.”

From reading this, I don’t think the developer would expect to get a 403 error using a PAT (for the simple reason that it implies that it works that way!)

Earlier, I mentioned that the permission is granted in terms of scopes. In this case, it looks like the scope is “r:devices”. Furthermore, the first link (about scopes) says that “r:devices” is applicable to both PAT and SmartApp. It doesn’t say, oh and by the way, that might or might not be true depending on whether you are using the /subscriptions or the /schedules endpoint (which works for PAT). Since both /subscriptions and /schedules are defined within an /installedAppId it makes perfect sense that a scope/token combination should work in the same way in both. It’s about the consistency of the permissions model. Consistency facilitates predictability. Predictability facilitates effective security.

The fact that the PAT lasts forever is not relevant. Passwords on most websites last forever. They only need is to be kept secret and revocable. SmartApp tokens need to have a short lifetime because they are sprayed all over external logs in AWS and Webhook hosts. (I hope the PATs are not all over Samsung’s internal logs!)

So in summary, I think the documentation’s version of how it should behave is conceptually correct. It’s the implementation that needs to be fixed.

Hello @wtsang01,

Even if both tokens have the same scopes configured (because both are OAuth2), they are generated for different purposes. You can see below an example of how they differ:

The personal access token (PAT) grants access to all the elements in your SmartThings ecosystem (devices, rules, rooms, apps, etc. at every location).

On the other hand, the SmartApp token just gives you access to the devices and automations that belong to the location where the SmartApp has been installed.

Both use the same SmartThings API requests, the type of token makes the result different.

For other references:

The simple SmartApp sample shows how it accesses context.api, to create subscriptions through the SmartApp; and, on the SmartApp NodeJS SDK docs, you can find how to interact with the event handlers.

I already take that into account in my previous message (I thought it was clear). I believe that your point is:

  • tokens of type A (PAT) are granted permission P (r:devices) to a resource set X
  • tokens of type B (SmartApp) are granted permission P to a resource set Y

I agree with that. I don’t agree with your deduction:

Both use the same SmartThings API requests, the type of token makes the result different.

That’s a design choice not a consequence. And an arbitrary one.

My point isthat if a resource belongs to both X and Y (a button at my location, where the SmartApp is installed) and is granted to tokens of either type, then they should both do the same thing, subject to the applicability rule, which in the case of r:devices is documented as (I repeat) https://smartthings.developer.samsung.com/docs/api-ref/st-api.html#operation/saveSubscription:

“r:devices:* - Read details about a device, including device attribute state. For SmartApp tokens , the scope is restricted to the location the SmartApp is installed into. For personal access tokens , the scope is limited to the account associated with the token. This scope is required to create subscriptions.”

The subtle suggestion I was making was that this is how the API should behave, and it should be consistently applied across endpoints /subscriptions and /schedules. You are clearly not receptive to that.

The less subtle point (where I live we tend to use understatement a lot) was that this is how the API should behave because that is what the documentation says, and it does not say it will respond with error 403. So would you be receptive to either:

  • change the documentation for subscriptions to reflect what the behaviour is.
  • change the behaviour so that it does what the documentation says

No doubt the first option is easier…

Hello @wtsang01,

Thank you for your feedback, all these replies help other users that might be interested in knowing more about how the tokens work.

We are working on updating the documentation, we appreciate your suggestions.

I completely agree with @wtsang01 on this. Unsurprisingly.

Why can’t i subscribe to my device’s events with my personal token? I can’t find any way to do this other than with a SmartApp token (i could be wrong on this, but i couldn’t locate anything in the docs).

The issue here is it’s over-engineering, with the best of intentions (device security). It creates unnecessary confusion. The security issue is almost moot: if you revoke a token or it becomes invalid, it cannot have any further access to the device, and the simple fact it’s short-lived is the reason it’s sprayed all over AWS logs. My Mongo instance has hundreds of token records now, from recording them each time when an event is received.

The way it works in my experience across other platforms is objects (on a graph) are linked to a user account, and that user explicitly grants access to their resources via a 3rd party app, which requests certain scopes. They are free to revoke that access at any time.

The use case i have with our app is we have 5-10 devices per location, which we need to subscribe to. When a user “installs” the smartapp, we get an INSTALL payload with 10 device IDs. Then, we need to do 10 GET calls to retrieve their details, then another 10 POST calls to subscribe to their events, and 5-10 POST calls to do schedules. For each EVENT payload, we have to use more POST calls to run commands.

All of that has to happen in the few seconds the user grants permission on the last screen in the SmartThings app. To avoid a blocking op, those calls have to run async in a background queue - which could potentially be thousands of jobs, at scale. It could be over 5mins for the job to get to the front of the queue, meaning the SmartApp access token has expired. The only way to mitigate that is to look for a more recent token from an EVENT payload.

Other platforms have to store their access tokens securely to prevent abuse as a matter of course. If necessary, the lifetime could be reduced to an hour (e.g. a default TTL for JWT). But why 5 minutes? What’s the point?

Thanks for the agreement @Alex_Cstr8. Having spent my 79 pounds on a hub, I was hoping to find a more receptive atmosphere for customer requests, but instead it seems that we are just told what it does.

I come at it from slightly different angle because what really bothers me is the lack of consistency and coherence with this approach.

It’s not controversial to suggest that good design involves separation of duties and each module doing one thing and doing it properly. So naturally you would expect the /subscriptions and the /schedules endpoints to be focussed on handling device events in the first case, and unleashing 80% of the power of cron in the second. Meanwhile there’s an authentication module that looks at a token and decides what permissions it has if any (it doesn’t know anything about what endpoints there are on the system). Meanwhile there’s an authorization module that knows which permissions which endpoint module requires in order to be called (it doesn’t know anything about how many different mechanisms there are to obtain a token - PAT, app, cereal packet, etc). None of the modules know anything about what job the others do. They don’t need to and they shouldn’t. And ultimately the dumb controller that connects it all together, as long as it’s checked that the Authenticator is happy with the token and knows what permissions it carries, that the Authorization is happy that the permissions are ok for calling that endpoint, then it goes ahead with the call. It really shouldn’t need to know much more than that. Certainly not business logic or permissions models.

But in the world of the Smartthings API, the /subscriptions end point is documented as needing the r:devices permission. But it only works for one type of token. The /schedules endpoint [deprecated now, but there is a r:schedules permission] and it works for both types of token. So where is that differential logic implemented? Are the endpoints really taking an interest in what type of token is coming in? I hope not! Is the Authenticator really saying, I’ll tell you what permissions this token has, but only if you tell me where you got it from? I hope not. And Authorization doesn’t even know what a token is, all it cares about is what permissions are required to call what endpoint. So where is that logic implemented? Maybe the controller. But as already said it shouldn’t really need to know what the permissions model is.

But it’s clearly implemented somewhere. Maybe the endpoints are even managed by different Authorization mechanisms. Wherever it’s implemented it’s been implemented more than once, because you need two of anything to show a difference.

Probably not quite as momentous as Rutherford’s realisation about the alpha particles that bounced back at him, but I reckon it’s a sign that somethings not quite as it should be.

The Documentation is correct as it is. This particular user would like to make a change request that the system does what it says.

@wtsang01

The 5 minute token is just a disposable token meant to be used in reaction to subscribed event firing. One can also generate a 30 day refresh token for accessing the API outside the scope of subscribed events

@Alex_Cstr8

Your issue seems a bit different. It sounds more like an oAuth application where you are managing multiple subscriptions on behalf of different user/location principles. An OAuth app with callbacks is something we are working on. If you could PM me with the details/requires for your APP I can provide more detailed information.