Requesting Haiilo API access

Manifest

A plug-in requests Haiilo API access via the manifest in the 'apiAccess' section. The following is an example of how this section could look like in the manifest.json

"apiAccess": {
  "url": "/event/",
  "client": {
    "mode": [
      "authorization_code"
    ],
    "scope": [
      "plugin:notify"
    ]
  }
}
AttributeTypeDescription
urlURLAbsolute or relative (to this manifest) URL to the base URL where the access token event is sent to
clientObjectClient-based access
client.modeString[]Requested authorisation modes:
- authorization_code: OAuth 2 "authorization_code" grant flow
client.scopeString[]Requested scopes:
- "plugin:notify": Access to the user notification API

Currently the only supported authorisation mode is "authorization_code". The access_token event will contain an authorization code that can be used to get an API access token using the "authorization_code" grant.

The scopes can currently contain just one entry: "plugin:notify", which grants access to the user notification API.

API access event

access_token

The access_token event will be sent on installation of a plug-in right after the install event. Haiilo Home also waits for a successful response (status 201) from the plug-in backend. If a failure response is sent then the whole installation will fail and the plug-in will immediately receive an uninstall event afterwards.

Same applies to a plug-in update when the manifest now contains an "apiAccess" section where it hadn't before. The only difference is that upon a failure no uninstall event is sent, just the update will fail.

The event is a JWT token, the payload will contain a "client" object containing all data that is necessary to get the OAuth 2 Haiilo API access token.

For example the following (incomplete) access_token event sent to https://yourplugin.yourcompany.test/event/access_token :

{
  "jti": "GfJ5qBpusTSAbNlNiy9pmVJVNDi7jRil",
  "sub": "access_token",
  "tenantId": "fbb6960d-9e8f-4f23-aa74-f903c3c36cef",
  "pluginId": "2c525b44-346f-4268-9ff3-b8b2f0c2c515",
  "base_url": "https://sometenant.coyocloud.com",
  "client": {
    "client_id": "plugins",
    "client_secret": "supersecret",
    "authorization_code": "39vjx2",
    "token_endpoint_url": "/api/oauth/token",
    "trigger_event": {
      "url": "/api/plugins/2c525b44-346f-4268-9ff3-b8b2f0c2c515/access_token/fbb6960d-9e8f-4f23-aa74-f903c3c36cef",
      "method": "POST",
      "body": "{\"token\":\"gf89haUZEW23DA2h\"}"
    }
  }
}

would result in the following OAuth 2 call the plug-in has to make to get the Haiilo API access token:

POST https://sometenant.coyocloud.com/api/oauth/token?grant_type=authorization_code&code=39vjx2
Authorization: Basic cGx1Z2luczpzdXBlcnNlY3JldA==

The access token response could look like this:

{
    "access_token": "eyJh...",
    "token_type": "bearer",
    "refresh_token": "eyJh...",
    "expires_in": 599,
    "scope": "plugin:notify",
    "tenant": "fbb6960d-9e8f-4f23-aa74-f903c3c36cef",
    "jti": "hhRDnGAkErDKUNL2xrWKTZkvOEQd5T6P"
}

The plug-in must check the granted scopes against the requested ones. If an essential scope is missing the plug-in may deny installation or update by responding to the access_token event with a failure code.

Please note that a refresh token is optional.

Recovery in case of expired token

If the plug-in backend fails to refresh the access token or in case the access token expired and there was no refresh token, then it can trigger a new access_token event by sending a request to the endpoint given in the "trigger_event" section.

POST https://sometenant.coyocloud.com/api/plugins/2c525b44-346f-4268-9ff3-b8b2f0c2c515/access_token/fbb6960d-9e8f-4f23-aa74-f903c3c36cef
Content-Type: application/json;charset=UTF-8

{"token":"gf89haUZEW23DA2h"}

The call is asynchronous and just triggers a new access_token event, it will not return a new one. The plug-in must ensure a proper synchronisation in the data flow by itself.

Be prepared that an access_token event could be received at any time, even without being triggered by the plug-in. A possible cause for this could be the revocation of an access token.

OAuth 2 considerations

OAuth 2 authentication requires a proper handling when tokens expire, especially when dealing with concurrent API calls.

Imagine two notification requests being sent in parallel with the same access token and the first one returns with a 401, signalling that the access token expired. The second call will experience the same. If now both calls trigger a refresh then they will interfere with each other. Instead The first 401 should result in all other calls to be queued and returning failed requests to be re-queued while the refresh is in progress. The queued calls must then proceed with the new access token.

We configured the Haiilo authorization server to produce new access tokens on each refresh. This enables using a token management strategy that allows a refresh without having to react to API requests failing with an 401 "Unauthorized" error. Access tokens could be cached in the plug-in with an expiry time some seconds (e.g. 10) before the actual expiry time. If the cache does not return an access token, the plug-in should perform a refresh and use the new access token for the request instead. That way the use of a valid access token is always ensured, and it reduces protocol handling overhead. At the same time the plug-in must ensure that refreshes do not happen too frequently and that the overlap time between the old and the new access token is not too large.