TL;DR: OPC UA Part 18: Role-Based Security
Full spec: reference.opcfoundation.org/Core/Part18 · Version 1.05.06 · Published 2025-10-22
NodeOPCUA Implementation: For a practical guide to implementing this specification in Node.js/TypeScript using NodeOPCUA 2.174.0, including a working 30-line code example and a details matrix of Part 18 spec-conformance, see the blog post OPC UA Role-Based Security in Practice.
What Is It?
OPC UA Part 18 defines the Information Model for role-based security, which is the standard way a Server decides who is allowed to do what in its AddressSpace.
It does not invent new permissions (those live in Part 3). Instead it defines the plumbing that connects identities (who is connecting) to Roles (named buckets of privileges), plus a standard API to manage local users.
Think of it as the "RBAC engine" of OPC UA: the same idea as Unix groups, Windows security groups, or cloud IAM roles, applied to an OPC UA Server.
flowchart LR
ID["👤 Identity<br/>(user token + app cert<br/>+ endpoint)"]
ROLE["🎭 Role<br/>(Operator, Engineer,<br/>SecurityAdmin...)"]
PERM["🔑 Permissions<br/>(Read, Write, Call,<br/>Browse... per Node)"]
ID -->|"mapping rules<br/>(Part 18)"| ROLE
ROLE -->|"RolePermissions<br/>(Part 3)"| PERM
style ID fill:#1d3557,color:#fff
style ROLE fill:#2d6a4f,color:#fff
style PERM fill:#6a040f,color:#fff
The Big Idea: Separate Authentication from Authorization
The whole point of Part 18 is to decouple two questions that are easy to tangle:
| Question | Term | Who answers it | Where |
|---|---|---|---|
| "Who are you?" | Authentication | User token + application certificate | Session creation (Part 4) |
| "What may you do?" | Authorization | Permissions granted to your Role(s) | Per-Node (Part 3) |
By splitting them, a Server can let a centralized service manage user identities and credentials, while the Server itself only ever manages Permissions for Roles on its Nodes. Add a new engineer to the directory → they automatically get the Engineer Role → they inherit every permission that Role was granted. The Server never stores that person's password.
How a Session Gets Its Roles
When a Client activates a Session, the Server evaluates every Role's mapping rules against the Session's properties and grants all Roles that match.
flowchart TB
START["Session activated"]
START --> CHECK{"For each Role:<br/>do ALL conditions match?"}
CHECK --> C1["① UserIdentityToken<br/>complies with<br/><b>Identities</b> rules"]
CHECK --> C2["② Client Certificate<br/>complies with<br/><b>Applications</b> filter"]
CHECK --> C3["③ Endpoint used<br/>complies with<br/><b>Endpoints</b> filter"]
C1 --> GRANT
C2 --> GRANT
C3 --> GRANT
GRANT{"All three true?"}
GRANT -->|Yes| Y["✅ Role granted<br/>to Session"]
GRANT -->|No| N["⛔ Role not granted"]
style Y fill:#2d6a4f,color:#fff
style N fill:#6a040f,color:#fff
Always-on default: the
AnonymousRole is assigned to every Session, no matter what. If a Role'sIdentitieslist is empty andCustomConfigurationis notTRUE, that Role can never be granted to anyone.
Re-evaluation: if a Role's configuration changes while Sessions are active, the Server re-evaluates and re-applies Role assignments to those Sessions immediately.
The Role Model: Information Model Map
flowchart TB
SC["ServerCapabilities<br/>(Part 5)"]
SC -->|HasComponent| RS["RoleSet : RoleSetType"]
RS -->|"AddRole()"| ADD["➕ add a Role"]
RS -->|"RemoveRole()"| REM["➖ remove a Role"]
RS -->|HasComponent| R1["Operator : RoleType"]
RS -->|HasComponent| R2["Engineer : RoleType"]
RS -->|HasComponent| R3["SecurityAdmin : RoleType"]
RS -.->|"... + well-known Roles"| DOTS["..."]
R1 --> PROPS["Properties + Methods<br/>(see RoleType below)"]
style RS fill:#2d6a4f,color:#fff
style PROPS fill:#1d3557,color:#fff
RoleSetType: the container of Roles
The RoleSet Object lives under Server.ServerCapabilities and publishes all Roles the Server supports.
| Member | Kind | Purpose |
|---|---|---|
<RoleName> | Object (RoleType) | One per Role, acting as a placeholder for any number |
AddRole(RoleName, NamespaceUri) → RoleNodeId | Method | Configuration Clients add a new Role |
RemoveRole(RoleNodeId) | Method | Remove a Role (deletes all its Permissions) |
⚠️ Security gate on every admin Method in Part 18: the Client shall use an encrypted channel and present credentials with admin rights (e.g. the
SecurityAdminRole). Otherwise →Bad_SecurityModeInsufficientorBad_UserAccessDenied.
The Well-Known Roles
Part 18 ships a standard ladder of Roles (NodeIds defined in Part 6). Servers should support them so Clients get predictable behavior across vendors.
flowchart TB
A["Anonymous<br/><i>always assigned</i>"]
B["AuthenticatedUser<br/><i>any valid credential</i>"]
T["TrustedApplication<br/><i>trusted app cert + signed channel</i>"]
O["Observer: read and subscribe"]
OP["Operator: interact with process"]
E["Engineer: configure and maintain"]
S["Supervisor: oversee operations"]
CA["ConfigureAdmin: change server config"]
SA["SecurityAdmin: change security config"]
A --> B --> O --> OP --> E --> S
B --> T
E --> CA
CA --> SA
style A fill:#495057,color:#fff
style SA fill:#6a040f,color:#fff
style CA fill:#9d0208,color:#fff
| Role | Default Identity criteria | Typical privilege |
|---|---|---|
| Anonymous | Anonymous + AuthenticatedUser | Default for all Sessions |
| AuthenticatedUser | AuthenticatedUser | Anyone who provided valid credentials |
| TrustedApplication | TrustedApplication | Any Client with a trusted app cert + signed channel |
| Observer | (configured) | Read values, browse, subscribe |
| Operator | (configured) | Operate the process (write setpoints, ack alarms) |
| Engineer | (configured) | Engineering / maintenance changes |
| Supervisor | (configured) | Supervisory oversight |
| ConfigureAdmin | (configured) | Modify server configuration |
| SecurityAdmin | (configured) | Modify security configuration |
🔒 Immutable trio: a Server shall not allow changes to
Anonymous,AuthenticatedUser, orTrustedApplication, and shall not allow deletion ofAnonymous/AuthenticatedUser/TrustedApplication.
RoleType: Anatomy of a Role
Each Role Object is a RoleType with the following Properties and configuration Methods. All of it is sensitive, meaning it is browseable, readable, writeable, and callable only by authorized admins over an encrypted channel.
Role : RoleType
├── Identities IdentityMappingRuleType[] ← WHO maps to this Role (Mandatory)
├── ApplicationsExclude Boolean ← treat Applications as deny-list? (Optional)
├── Applications String[] (ApplicationUris) ← WHICH client apps (Optional)
├── EndpointsExclude Boolean ← treat Endpoints as deny-list? (Optional)
├── Endpoints EndpointType[] ← WHICH endpoints (Optional)
├── CustomConfiguration Boolean ← vendor-specific mapping?(Optional)
│
├── AddIdentity(Rule) RemoveIdentity(Rule) ← manage identity rules
├── AddApplication(AppUri) RemoveApplication(AppUri) ← manage app filter
└── AddEndpoint(Endpoint) RemoveEndpoint(Endpoint) ← manage endpoint filter
The three filters, and how *Exclude flips them
Applications and Endpoints are filter lists whose meaning is flipped by their companion *Exclude flag:
*Exclude value | List behaves as | Meaning |
|---|---|---|
FALSE (or absent) | Include / allow-list | Only items in the list qualify for the Role |
TRUE | Exclude / deny-list | Everything except the list qualifies |
Default
*ExcludeisTRUEwhen the list is empty (so an empty list = "everyone qualifies"). IfApplicationshas any entry, the Role is only granted over a signed (or signed-and-encrypted) channel.
✍️ Configuration rule:
Identities,Applications, andEndpointsare read-only via the Write Service (CurrentWriteisFALSE); they must be changed through theAdd*/Remove*Methods. OnlyApplicationsExclude/EndpointsExcludeare set with plain Writes.
CustomConfiguration: the escape hatch
If a Server wants to support the RolePermissions Attribute but can't implement the standard mapping machinery, it sets CustomConfiguration = TRUE. It may then hide the standard config or expose vendor-specific options. Roles are required to support RolePermissions; this flag is how a Server says "the mapping is mine to manage."
Identity Mapping Rules: the Heart of "Who Maps to What"
An IdentityMappingRuleType is a single (criteriaType, criteria) rule. A Role's Identities Property is an array of these.
IdentityMappingRuleType {
criteriaType : IdentityCriteriaType ← what kind of match
criteria : String ← the value to match (null/empty for the 3 "any" types)
}
The IdentityCriteriaType enumeration
flowchart LR
subgraph who["Match a specific person"]
UN["UserName (1)<br/>OS or server-managed user"]
TP["Thumbprint (2)<br/>user cert thumbprint (hex)"]
X5["X509Subject (8)<br/>cert subject / issuer name"]
end
subgraph token["Match a claim in an Access Token"]
RO["Role (3)<br/>role claim in JWT"]
GR["GroupId (4)<br/>group claim in JWT"]
end
subgraph any["Match a category"]
AN["Anonymous (5)<br/>no credentials"]
AU["AuthenticatedUser (6)<br/>any valid credentials"]
AP["Application (7)<br/>specific ApplicationUri"]
TA["TrustedApplication (9)<br/>any trusted app cert"]
end
| Value | criteriaType | What criteria holds |
|---|---|---|
| 1 | UserName | A user name known to the Server (OS account or server-managed, see §5) |
| 2 | Thumbprint | Hex thumbprint (upper-case, no spaces) of a user Certificate |
| 3 | Role | A role entry from the Access Token (roles array of a JWT; prefixed by iss/ if present) |
| 4 | GroupId | A group entry from the Access Token (groups array of a JWT; e.g. AD security group) |
| 5 | Anonymous | (empty), applies when no credentials were provided |
| 6 | AuthenticatedUser | (empty), applies when any valid credentials were provided |
| 7 | Application | An ApplicationUri from a trusted Client Certificate (signed channel required) |
| 8 | X509Subject | A structured subject name, e.g. CN="User Name"/O="Company" |
| 9 | TrustedApplication | (empty), any trusted app cert over a signed/encrypted channel |
🛡️ Sane-default guidance: the Server should refuse dangerous combinations, such as adding an
Anonymous (5)mapping rule to an administrator Role.AddIdentitymay returnBad_RequestNotAllowedfor exactly this reason.
X509Subject ordering (Table 10)
When matching X509Subject, the subject name is normalized into a fixed /-separated order, each value double-quoted:
CN → O → OU → DC → L → S → C → dnQualifier → serialNumber
Only these names are compared; others are ignored. Repeated names appear in certificate order. Example: CN="Jane Doe"/O="Sterfive"/C="FR".
EndpointType: Filtering by Connection
Endpoints entries are EndpointType structures. Only non-default fields are compared, so you can match loosely (just a URL) or tightly (URL + security mode + policy + transport).
EndpointType {
endpointUrl String ← the endpoint URL
securityMode MessageSecurityMode ← default Invalid → ignored if default
securityPolicyUri String ← default empty → ignored if default
transportProfileUri String ← default empty → ignored if default
}
Use case: grant Operator only when the Session connects through the encrypted production endpoint, never through a diagnostic one.
Auditing Role Changes
Every successful Add*/Remove* call on a Role raises a RoleMappingRuleChangedAuditEventType (abstract subtype of AuditUpdateMethodEventType). This gives you a tamper-evident trail of who changed which mapping rule, when, which is essential for security compliance.
sequenceDiagram
participant Admin as 🛠️ SecurityAdmin
participant Srv as 🖥️ Server
participant Sub as 📋 Audit Subscriber
Admin->>Srv: AddIdentity(Operator, {UserName,"jdoe"})<br/>(encrypted channel)
Srv->>Srv: Update Operator.Identities
Srv->>Srv: Re-evaluate active Sessions
Srv-->>Sub: RoleMappingRuleChangedAuditEvent
Note over Sub: who / when / which method / which Role
User Management Model (§5): Local Users for UserName Rules
The UserName criteriaType needs the Server to verify a name + password locally. Part 18 §5 defines a standard API to manage that user list, exposed as a UserManagement Object under ServerConfiguration (from Part 12).
ServerConfiguration (Part 12)
└── UserManagement : UserManagementType
├── Users UserManagementDataType[] ← the user list (admin-only) (Mandatory)
├── PasswordLength Range ← min/max length (0 = no limit)(Mandatory)
├── PasswordOptions PasswordOptionsMask ← server password features (Mandatory)
├── PasswordRestrictions LocalizedText ← human-readable rules (Optional)
│
├── AddUser(UserName, Password, UserConfiguration, Description) ← admin
├── ModifyUser(UserName, ModifyPassword, Password, ...) ← admin
├── RemoveUser(UserName) ← admin
└── ChangePassword(OldPassword, NewPassword) ← the user themselves
Who can call what
| Method | Caller | Notes |
|---|---|---|
AddUser / ModifyUser / RemoveUser | Admin (e.g. SecurityAdmin) | Encrypted channel; sensitive |
ChangePassword | The Session user | Only if the user token type is USERNAME; encrypted channel |
Removing or disabling a user closes all their Sessions and Subscriptions. You cannot remove or disable yourself; that action fails with
Bad_InvalidSelfReference.
UserConfigurationMask: per-user flags
| Bit | Flag | Meaning |
|---|---|---|
| 0 | NoDelete | User cannot be deleted |
| 1 | Disabled | User behaves as non-existent for ActivateSession |
| 2 | NoChangeByUser | User cannot change their own password |
| 3 | MustChangePassword | User must change password before getting Roles (invalid together with NoChangeByUser) |
PasswordOptionsMask: server password policy
Advertises what the Server supports / requires: initial-password-change, disable-user, no-delete, no-change, descriptions, and complexity rules (RequiresUpperCase, RequiresLowerCase, RequiresDigit, RequiresSpecialCharacters). If the Server has no special requirements, all bits are FALSE.
The "must change password" dance
sequenceDiagram
participant C as 👤 Client
participant S as 🖥️ Server
C->>S: ActivateSession(user, oldPassword)
S-->>C: Good_PasswordChangeRequired
Note over C,S: Session has ONLY the Anonymous Role for now
C->>S: ChangePassword(oldPassword, newPassword)
S-->>C: Good (MustChangePassword → FALSE)
C->>S: ActivateSession(user, newPassword)
S-->>C: Good ✅ now gets the user's real Roles
ChangePasswordis callable via well-known NodeIds even when it isn't browseable for that user, so a locked-out, must-change user can still reach it.
Putting It All Together: End-to-End
flowchart TB
subgraph connect["1 · Connect & Authenticate (Part 4)"]
U["👤 User token<br/>(UserName / Cert / JWT)"]
AC["📜 Application cert"]
EP["🔌 Endpoint"]
end
subgraph map["2 · Map to Roles (Part 18)"]
RULES["Evaluate every Role's<br/>Identities + Applications + Endpoints"]
end
subgraph roles["3 · Roles granted"]
GR["🎭 e.g. {Anonymous, Operator}"]
end
subgraph authz["4 · Authorize each request (Part 3)"]
RP["RolePermissions on the target Node<br/>→ Read? Write? Call? Browse?"]
end
connect --> RULES --> GR --> RP
RP -->|granted| OK["✅ Service succeeds"]
RP -->|denied| NO["⛔ Bad_UserAccessDenied"]
style GR fill:#2d6a4f,color:#fff
style OK fill:#2d6a4f,color:#fff
style NO fill:#6a040f,color:#fff
Key Takeaways
- Part 18 = the RBAC engine: it maps identities → Roles; Part 3 maps Roles → Permissions per Node. Two halves of one access-control story.
- Authentication ≠ Authorization: splitting them lets a Server delegate identity to a central service while owning only its Node Permissions.
- A Session can hold many Roles: granted when all three filters (Identities and Applications and Endpoints) match.
Anonymousis always there. - Mapping rules are configured by Methods, not Writes:
AddIdentity/AddApplication/AddEndpoint, all admin-only over encrypted channels. *Excludeflips lists between allow-list and deny-list, and an empty list defaults to "everyone."- Nine ways to identify a caller: from a literal
UserNameto JWTRole/GroupIdclaims toX509Subjectmatching. - Well-known Roles are a portable ladder:
Observer → Operator → Engineer → Supervisor → ConfigureAdmin → SecurityAdmin; theAnonymous/AuthenticatedUser/TrustedApplicationtrio is immutable. - Every change is audited via
RoleMappingRuleChangedAuditEventType. - §5 adds a standard local user store:
AddUser/ModifyUser/RemoveUser/ChangePassword, with password policy flags and a "must change password" flow. - You can't lock yourself out: disabling or removing your own user fails with
Bad_InvalidSelfReference.
Relationship to Other Parts
flowchart TB
P1["Part 1: Concepts"]
P3["Part 3: Address Space<br/>Permissions, RolePermissions Attr"]
P4["Part 4: Services<br/>Sessions, user tokens, MessageSecurityMode"]
P5["Part 5: Information Model<br/>BaseObjectType, ServerCapabilities, RoleSet"]
P6["Part 6: Mappings<br/>well-known Role NodeIds, JWT→token groups"]
P8["Part 8: Data Access<br/>Range DataType"]
P12["Part 12: Discovery / GDS<br/>ServerConfiguration Object"]
P18["Part 18: Role-Based Security<br/>RoleSet, RoleType, mapping rules,<br/>UserManagement"]
P18 -->|grants Permissions defined by| P3
P18 -->|consumes Sessions/tokens from| P4
P18 -->|RoleSet hangs off ServerCapabilities| P5
P18 -->|uses well-known NodeIds + JWT rules| P6
P18 -->|PasswordLength uses Range| P8
P18 -->|UserManagement hangs off ServerConfiguration| P12
style P18 fill:#2d6a4f,color:#fff
style P3 fill:#1d3557,color:#fff
| Part | What Part 18 borrows from it |
|---|---|
| Part 3 | The actual Permissions and the RolePermissions Node Attribute that Roles are granted |
| Part 4 | Session/ActivateSession, user identity tokens, MessageSecurityMode |
| Part 5 | BaseObjectType, ServerCapabilities (host of RoleSet), AuditUpdateMethodEventType |
| Part 6 | Well-known Role NodeIds; how AD groups/roles are added to Access Tokens (JWT) |
| Part 8 | Range DataType (used by PasswordLength) |
| Part 12 | ServerConfiguration Object (host of UserManagement) |
TL;DR generated from OPC 10000-18 v1.05.06 (2025-10-22). For normative wording, including exact result codes, conformance units, and table definitions, always consult the official specification.