Appearance
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:
- Who is calling? (user vs. API key)
- On behalf of which tenant? (group + role)
- 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:
- An administrator calls the
Create UserendpointPOST /api/v1/iam/groups/:groupName/roles/:roleName/users
and receives an invitationcodeplus the reservedname. - 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:
- The user receives an invite (e‑mail, letter, support ticket, …) containing:
usernamecode- base URL of your backend
- You present a sign-up form that collects:
- username (pre-filled if possible)
- code
- password (validated against the password rules)
- 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
- User requests enabling 2FA in your UI.
- Frontend calls
POST /api/v1/iam/2fa/enablewith the current password. - Response contains:
secretanduri(for displaying a QR code in an authenticator app)- a temporary
tokenused for verification
- UI shows the QR code / secret and asks the user for the current one-time code.
- Frontend calls
POST /api/v1/iam/2fa/verifywith:- the
tokenfrom step 3 - the one-time
codefrom the authenticator app
- the
If verification succeeds, 2FA is active for the user.
Login with 2FA enabled
- User enters username + password.
- Your login backend calls
/api/v1/iam/tokenas usual. - The backend indicates that a second factor is required (by returning a temporary token).
- Your UI asks the user for their one-time code and sends it to:
POST /api/v1/iam/2fa/tokenwith:- the temporary
token - the one-time
code
- the temporary
- 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:
readon resourcemetaupdateon resourceservicecreateon resourcealarms.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
deletealarms.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/infoif 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.useriam.pair.roleiam.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
- User opens “Add device” wizard.
- The UI asks the user to:
- scan a QR code,
- enter a serial number,
- or otherwise provide the deviceIdentifier.
- 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.)
- The backend decides where to attach the device (user, role, or group), based on the token’s pairing permissions.
- The UI refreshes:
- local device lists (e.g. via
/api/v1/iam/pairor/api/v1/devices), - relevant dashboards and service views.
- local device lists (e.g. via
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.key– the actual secret, formatted asprefix.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
durationfield 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:
Startup
- Check for an existing session (access + refresh token, or a server-side session).
- If none exists, redirect to the login / sign-up flow.
Login / sign-up
- Handle sign-up via
/api/v1/iam/signupif 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.
- Handle sign-up via
Data access
- Use
Authorization: Bearer <<TOKEN>>for:/api/v1/schemasto understand services and properties./api/v1/devicesor/api/v1/servicesfor the current state./api/v1/metafor device metadata./api/v1/streams//api/v1/timestream/ history endpoints as needed.
- Respect 403 responses as a signal of missing permissions.
- Use
Device pairing (if supported)
- Provide a “pair device” flow using
/api/v1/iam/pair. - Refresh device lists after successful pairing or unpairing.
- Provide a “pair device” flow using
Session maintenance
- Use refresh tokens via
POST /api/v1/iam/token?grant_type=refresh_token&scope=offline. - Optionally call
/api/v1/iam/token/infoto show session status. - Gracefully handle expired or revoked tokens by redirecting to login.
- Use refresh tokens via
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.