Skip to content

Authorization Model

Effective permission = role permissions ∩ token scopes

An operator with an ads:read-only token can only read.

Roles

RolePermissions
viewerRead variables, browse symbols, device info, read files
operator+ Write variables, upload files
admin+ Start/stop/reset PLC

Scopes

ScopeGrants
ads:readAll read operations
ads:writeWrite operations (variables, files)
ads:lifecyclePLC lifecycle control

Policies

PolicyRequirements
ReadAccessplc_access check + role >= viewer + scope ads:read
WriteAccessplc_access check + role >= operator + scope ads:write
LifecycleAccessplc_access check + role = admin + scope ads:lifecycle

Per-PLC Access Control

Add a plc_access claim to the JWT with an array of allowed PLC aliases:

{
  "plc_access": ["Line1", "TestRig"]
}
  • If plc_access is present, the user can only access listed PLCs
  • If plc_access is absent, the user can access no PLC (default deny)
  • plc_access: ["*"] reaches every PLC — how a service account that genuinely needs the whole fleet says so
  • Accessing a denied PLC returns 403 with PLC_ACCESS_DENIED

A token the IdP never restricted is not thereby a token authorised for everything. Read the other way round: without default-deny, a compromised token that happens to carry no claim holds unrestricted access to every configured PLC.

The rule is the same everywhere the claim is read — per-PLC routes, the fleet-wide alarm routes, and the MCP tools and resources.

A claim that is present but unreadable (malformed JSON, null) always denies, whatever the default is. The default governs absence only: an unreadable claim means the IdP meant to restrict the token and adsify cannot tell how.

Migrating a deployment whose IdP does not issue the claim

Breaking change. Before this, an absent plc_access claim granted access to every PLC. Tokens without the claim now receive 403 PLC_ACCESS_DENIED.

Add a plc_access claim to your tokens — see Keycloak for a protocol mapper. Until that is in place, a deployment can restore the previous behaviour:

{
  "Authorization": {
    "PlcAccessDefault": "allow"
  }
}
ValueAn absent plc_access claim means
deny (default)The caller reaches no PLC
allowThe caller reaches every configured PLC

allow logs a warning at every startup. It is a migration aid, not a supported end state.