Skip to content

Access Management (IAM) for Application Developers

Scope

This chapter explains how frontend applications and external services interact with the Identity and Access Management (IAM) module of the coldwave backend.

It focuses on:

  • Logging users in and out
  • Working with access and refresh tokens
  • Understanding permissions and device/service scoping
  • Pairing devices to users
  • Using API keys for backend-to-backend communication

Designing groups, roles and overall permissions for a deployment is considered an administrative task and is covered by the IAM module reference.

TIP

IAM can be disabled for a deployment. In that case, the backend accepts requests without any token and all access control must be handled on another level. The flows below only apply when IAM is enabled.


Core IAM concepts

Before looking at concrete flows, it helps to have a very small mental model of IAM:

  • Users – represent human users of your application.
  • Groups – usually represent a customer, tenant or organization.
  • Roles – define what a user (or key) is allowed to do (resources + actions).
  • Tokens – short-lived JWTs that carry the effective permissions for a user or API key.
  • API keys – credentials for non-interactive clients (CLI tools, backend jobs, etc.).
  • Pairings – relations between a user (or role/group) and individual devices.

Roughly, IAM answers three questions for the rest of the backend:

  1. Who is calling? (user vs. API key)
  2. On behalf of which tenant? (group + role)
  3. What and where are they allowed to access? (resources, actions, devices, services)

As an application developer, you usually only consume that information (via tokens and HTTP status codes), you do not maintain it.


Authentication: from invite to token

1. User creation and sign-up

User accounts are created in two steps:

  1. An administrator calls the Create User endpoint
    POST /api/v1/iam/groups/:groupName/roles/:roleName/users
    and receives an invitation code plus the reserved name.
  2. The user completes the registration via the public sign-up endpoint
    POST /api/v1/iam/signup.

Your frontend usually only implements step 2.

Typical sign-up flow in a UI:

  1. The user receives an invite (e‑mail, letter, support ticket, …) containing:
    • username
    • code
    • base URL of your backend
  2. You present a sign-up form that collects:
    • username (pre-filled if possible)
    • code
    • password (validated against the password rules)
  3. The frontend calls:
http
POST /api/v1/iam/signup
Content-Type: application/json

{
  "name": "<<USERNAME>>",
  "code": "<<CODE>>",
  "password": "<<PASSWORD>>"
}

If the request succeeds, the backend responds with HTTP status 204 No Content.
From that moment on the user can log in with username and password.

2. Logging in and receiving a token

To call most backend APIs, the frontend needs an access token (a signed JWT).
Tokens are retrieved at:

  • GET /api/v1/iam/token

The endpoint expects a Basic Authorization header containing the username and password. In a browser-based app you normally keep the actual login call on a backend or API gateway to avoid exposing passwords to the browser.

Example request (shown as curl):

bash
curl --location 'https://<<URL>>/api/v1/iam/token'   --header 'Authorization: Basic BASE64(user:password)'

Example response (simplified):

json
{
  "type": "TOKEN",
  "token": "eyJhbGciOiJSUzI1NiIs...",
  "refreshToken": "optional-refresh-token-if-requested"
}

Use the token value for all subsequent API requests as a Bearer token:

http
GET /api/v1/devices?depth=2&raw=true
Authorization: Bearer <<TOKEN>>

Optional: requesting a refresh token

If you add the query parameter scope=offline when calling /api/v1/iam/token, the response will include a refreshToken. This token can later be exchanged for a new access token without asking the user for credentials again.

http
GET /api/v1/iam/token?scope=offline
Authorization: Basic BASE64(user:password)

Your UI should then:

  • Store the access token in short-lived memory (or a secure cookie).
  • Store the refresh token in a more persistent but secure location (ideally server-side or in an HTTP-only cookie, not in localStorage).

Exchanging a refresh token

To exchange a refresh token, call:

  • POST /api/v1/iam/token?grant_type=refresh_token&scope=offline

with a JSON body:

http
POST /api/v1/iam/token?grant_type=refresh_token&scope=offline
Content-Type: application/json

{
  "refreshToken": "<<REFRESH_TOKEN>>"
}

The response again contains a new token and, optionally, a new refreshToken.

A typical frontend strategy is:

  • Keep track of the access token’s expiry time (from the JWT claims).
  • A bit before expiry, silently exchange the refresh token.
  • If both token and refresh token fail (401 responses), redirect to the login screen.

3. Two-factor authentication (2FA)

If the deployment uses 2FA, there are a few more steps on top of the flows above.

Enabling 2FA

  1. User requests enabling 2FA in your UI.
  2. Frontend calls POST /api/v1/iam/2fa/enable with the current password.
  3. Response contains:
    • secret and uri (for displaying a QR code in an authenticator app)
    • a temporary token used for verification
  4. UI shows the QR code / secret and asks the user for the current one-time code.
  5. Frontend calls POST /api/v1/iam/2fa/verify with:
    • the token from step 3
    • the one-time code from the authenticator app

If verification succeeds, 2FA is active for the user.

Login with 2FA enabled

  1. User enters username + password.
  2. Your login backend calls /api/v1/iam/token as usual.
  3. The backend indicates that a second factor is required (by returning a temporary token).
  4. Your UI asks the user for their one-time code and sends it to:
    • POST /api/v1/iam/2fa/token with:
      • the temporary token
      • the one-time code
  5. The response contains the regular access token (and optional refresh token).

Your frontend should:

  • Detect when 2FA is required and show an extra step.
  • Handle the same error cases (wrong code, expired temporary token) as for password login.

Authorization: permissions, devices and services

Once you have a token, authorization is completely driven by IAM and enforced by all modules.

1. Resources and actions

Each API endpoint documents the access it requires, for example:

  • read on resource meta
  • update on resource service
  • create on resource alarms.rules

In the IAM token, these appear as a list of permissions that combine:

  • resource – what feature or object is affected (meta, service, alarms.rules, iam.users, …)
  • actions – operations that are allowed (create, read, update, delete)

As an application developer you normally:

  • Do not modify these permissions directly.
  • Use them in two ways:
    • to interpret authorization errors (403 responses),
    • optionally to pre-filter UI elements (e.g. hide “Delete alarm” if the user can’t delete alarms.rules).

If you parse the JWT on the client side, make sure you only use it for hints (what to show). The backend remains the single source of truth for access decisions.

2. Device and service scoping

Besides generic permissions, IAM can further constrain access by:

  • a list of device identifiers
  • a list of service identifiers
  • or the wildcard * (meaning full access for that dimension)

These fields exist on groups, roles, users and API keys. The backend resolves them to the effective scope for a token. Example patterns:

  • A support role with access to “all devices of tenant A”.
  • A dashboard role that sees “only services X and Y, on a small whitelist of devices”.

The important implications for your app:

  • You can always call the regular endpoints (e.g. /api/v1/devices) and the backend will filter the result according to the token. You do not have to manually insert device or service lists as query parameters.
  • If a user does not see an expected device or service, it is usually a scoping issue in IAM, not a bug in your UI.
  • You can query IAM for token information via GET /api/v1/iam/token/info if you want to show a “session details” or “permissions” panel.

Device pairing in end-user flows

End users will often need to attach devices to their account (or role/group) after devices are deployed. For that, IAM exposes pairing endpoints.

At a high level:

  • A pairing expresses “user X (or their role/group) may access device Y”.
  • The backend chooses where to store the pairing (user, role, or group), based on the caller’s pairing permissions:
    • iam.pair.user
    • iam.pair.role
    • iam.pair.group

1. Pairing endpoints

Key endpoints for frontends:

  • POST /api/v1/iam/pair
    Add one or more device identifiers to the current user / role / group.
  • GET /api/v1/iam/pair
    List all device identifiers the current user can access.
  • DELETE /api/v1/iam/pair
    Remove pairings.

There are also endpoints for working with a single device identifier (/api/v1/iam/pair/:deviceIdentifier) which are more convenient if your UI always pairs one device at a time.

The documentation explains which fields are expected in the request body. Typically this is an array of device identifiers.

2. Typical “pair device” flow in a UI

  1. User opens “Add device” wizard.
  2. The UI asks the user to:
    • scan a QR code,
    • enter a serial number,
    • or otherwise provide the deviceIdentifier.
  3. The frontend calls:
http
POST /api/v1/iam/pair
Content-Type: application/json
Authorization: Bearer <<TOKEN>>

{
  "deviceIdentifier": ["CFF349041010ADC"]
}

(Exact field names: see IAM API reference. The deprecated /api/v1/pair endpoint used systemIdentifier; new endpoints consistently talk about deviceIdentifier.)

  1. The backend decides where to attach the device (user, role, or group), based on the token’s pairing permissions.
  2. The UI refreshes:
    • local device lists (e.g. via /api/v1/iam/pair or /api/v1/devices),
    • relevant dashboards and service views.

For removing a pairing, you call DELETE /api/v1/iam/pair with the corresponding device identifier(s).

Best practices:

  • After pairing, do not trust the local state. Always re-fetch the list of devices from the backend.
  • Be prepared to handle the case where pairing fails due to missing pairing permissions (HTTP 403) or unknown device identifiers (HTTP 404 / 400).

API keys for backend-to-backend access

For non-interactive clients (backend jobs, integration services, CLI tools) it is often more convenient to use API keys instead of user passwords.

1. API key lifecycle

Administrators create and manage keys via the IAM API:

  • POST /api/v1/iam/keys/groups/:groupName/roles/:roleName
    Create a new key for a specific group and role.
  • GET /api/v1/iam/keys
    List all keys.
  • GET /api/v1/iam/keys/:keyPrefix
    Get details for a single key by prefix.
  • PATCH /api/v1/iam/keys/:keyPrefix
    Rotate a key or change its duration.
  • DELETE /api/v1/iam/keys
    Delete all keys.
  • DELETE /api/v1/iam/keys/:keyPrefix
    Delete a single key.

When a key is created or rotated, the response contains:

  • group, role – what IAM role this key assumes.
  • prefix – the stable identifier of the key.
  • expiresAt – when the key will expire.
  • keythe actual secret, formatted as prefix.secret.

The key value is only returned once. It must be stored safely by whoever operates the non-interactive client.

IAM also publishes websocket events (e.g. IAM_KEY_ADD, IAM_KEY_UPDATE, IAM_KEY_DELETE) whenever keys change. This is useful if you run an admin UI that reacts live to changes.

2. Using API keys in clients

An API key is used “as a header” when calling the backend. The exact header name and scheme are deployment-specific; consult your deployment or integration documentation.

Typical patterns are:

  • a dedicated header (for example X-API-Key: prefix.secret), or
  • a custom authorization scheme (for example Authorization: ApiKey prefix.secret).

Regardless of the exact header name:

  • API key requests do not use username/password.
  • The backend treats them like a user token with the permissions of the configured group/role.
  • Device/service scoping works the same way as for regular users.

Recommendations:

  • Prefer short-lived keys by using the duration field when creating/updating keys.
  • Store keys in a secure secret store, not in source code.
  • Rotate keys on a regular schedule and rely on the websocket events or polling to propagate changes to admin UIs.

Putting it together in a frontend app

A minimal coldwave frontend that respects IAM usually follows this pattern:

  1. Startup

    • Check for an existing session (access + refresh token, or a server-side session).
    • If none exists, redirect to the login / sign-up flow.
  2. Login / sign-up

    • Handle sign-up via /api/v1/iam/signup if the user comes with an invitation code.
    • Handle login via /api/v1/iam/token (through your backend).
    • If 2FA is enabled for the user, add the extra step using the /api/v1/iam/2fa/* endpoints.
    • Store token information and start a refresh timer.
  3. Data access

    • Use Authorization: Bearer <<TOKEN>> for:
      • /api/v1/schemas to understand services and properties.
      • /api/v1/devices or /api/v1/services for the current state.
      • /api/v1/meta for device metadata.
      • /api/v1/streams / /api/v1/timestream / history endpoints as needed.
    • Respect 403 responses as a signal of missing permissions.
  4. Device pairing (if supported)

    • Provide a “pair device” flow using /api/v1/iam/pair.
    • Refresh device lists after successful pairing or unpairing.
  5. Session maintenance

    • Use refresh tokens via POST /api/v1/iam/token?grant_type=refresh_token&scope=offline.
    • Optionally call /api/v1/iam/token/info to show session status.
    • Gracefully handle expired or revoked tokens by redirecting to login.
  6. Integrations

    • For non-interactive clients, provision API keys via the IAM API.
    • Configure those clients to send the key as an HTTP header on every request.

With this in place, most of your day-to-day work can focus on rendering data (Schema, Service, Meta, Streams, History) while IAM ensures that every call is properly authenticated and scoped to the right tenant, devices and services.