Repository navigation
Server Access Control
Decide which operations each Session may perform and keep sampled permissions current.
Milo 1.2.0-SNAPSHOT, Java 17. API baseline 7faaf4f0516c.
Configure the identity validator described in Server security. It establishes an Identity for a Session. A RoleMapper maps identity, and optionally application URI and endpoint, to role NodeIds. Define the anonymous and unknown-user cases explicitly. Install the mapper with OpcUaServerConfigBuilder.setRoleMapper(...). Without it, Session.getRoleIds() is empty and the default controller performs no role checks.
Roles and RolePermissions metadata do not derive effective attributes on ordinary UaNode instances. Applications must expose UserAccessLevel, UserWriteMask, UserExecutable, and UserRolePermissions from their own policy, usually through attribute filters that consult the current Session. Session.getRoleIds() consults the role mapper on each call. Authentication success alone does not grant or deny an operation.
| Attribute | Default controller use |
|---|---|
AccessRestrictions |
Channel signing/encryption requirements; Browse checks these only when the Browse flag applies. |
AccessLevel, then UserAccessLevel
|
CurrentRead/CurrentWrite for Value reads/writes. |
UserWriteMask |
Non-Value attribute writes. |
UserExecutable |
Method execution. |
UserRolePermissions |
Role permissions for Browse, Call, AddReferences, DeleteReferences, DeleteNodes, and particular protected attributes. Call runs its role check only when both the Object and Method expose this attribute, then requires Call permission on both. Permissions on only one of them are not checked. |
A role check runs only when Session roles and node user-role permissions are present. Missing metadata is not deny-by-default. RolePermissions alone is not an access policy. Test every operation your policy intends to restrict, including Methods and browsing.
server.getAccessControlManager() owns the controller, read cache, and invalidation listeners. The supported service paths listed below check access before address-space dispatch. A Value read fails with Bad_NotReadable when the Variable lacks CurrentRead, or Bad_UserAccessDenied when only the user's CurrentRead is absent. Write similarly distinguishes Bad_NotWritable from user denial. Insufficient signing/encryption yields Bad_SecurityModeInsufficient.
Install a custom controller through setAccessControllerFactory(...) when the attribute model is insufficient. It can wrap DefaultAccessController; preserve baseline denials and AccessResult.NODE_UNKNOWN. Unknown means no decision, not a fresh grant. Direct address-space calls are below the service authorization boundary and require caller checks.
Authorization filters must be quick, thread-safe, and must not recursively call the controller whose attribute read they are answering. Evaluate the Session's current identity and endpoint. Session-listener callbacks are asynchronous and occur too late to be the sole source of truth for identity-change rechecks.
The controller is consulted for Read, Write, Browse, Call, CreateMonitoredItems, DeleteNodes, AddReferences, and DeleteReferences. BrowseNext returns the remainder already filtered by Browse and does not recheck access; permissions revoked between pages do not filter those remaining references. TransferSubscriptions rechecks read access for transferred data items. HistoryRead, HistoryUpdate, AddNodes, and TranslateBrowsePathsToNodeIds have no controller check; enforce policy for those operations in your address space. Path translation can resolve a NodeId that Browse hides. Per-event authorization is also application work.
A data MonitoredItem can be created successfully even when CurrentRead is denied. It reports Bad_NotReadable or Bad_UserAccessDenied, with no value, through Publish. Creation fails for insufficient channel security. An event item has no sampled DataValue to carry a denial, so creation rejects a denied event-monitoring request.
Each data item retains a read-access decision. setValue() gates delivery with that stored decision; it does not perform a new permission check. Framework sampling refreshes it before a cycle. On restoration, the next value can be reported even if it equals the value seen before denial.
The default sampling policy checks every cycle. ReadAccessPolicy.cached() uses a cache keyed by Session object, NodeId, and attribute. Entries have no time-to-live or size limit. Index range and encoding are not part of the key; rules depending on them need another policy.
Read, Write, Call, Browse, and CreateMonitoredItems ask the controller on every request and never use this cache. Only cached-policy samplers and components using getReadAccessCache() require invalidation to see a change. The Sampling configuration example selects the cached policy.
For this static Variable policy change, node is initially readable and is sampled by a cached-policy managed namespace. Import org.eclipse.milo.opcua.sdk.core.AccessLevel:
node.setUserAccessLevel(AccessLevel.toValue(AccessLevel.NONE));
server.getAccessControlManager().invalidateReadAccess(node.getNodeId());Reads report Bad_UserAccessDenied immediately after the attribute changes. Without invalidation, a cached sampler retains its earlier grant. Invalidation lets its next framework refresh publish the denial. To restore access, update the effective attribute and invalidate again. Direct Reads become Good at once; a cached denial persists until invalidation and refresh. A per-Session filter should invalidate the scope affected by its own inputs, not change a shared attribute to represent one user's permissions. invalidateReadAccess(ReadAccessScope) accepts scopes created by ReadAccessScope.node(...), nodes(...), namespace(...), or matching(...); forSession(session) narrows any scope to one Session. NodeId, Session, and server-wide overloads are shortcuts. A matching predicate runs under the cache write lock and must be fast and nonblocking. Removing a node and adding another with the same NodeId also requires invalidation.
SDK identity/endpoint changes, subscription transfer, and Session closure invalidate the relevant cached state. Applications invalidate their own changes to roles, ACLs, device locks, effective attributes, and custom policy inputs. Cache invalidation only drops decisions and notifies listeners. It does not wake samplers, cancel in-flight device reads, immediately update all items, or purge queued notifications. An ordinary policy revocation can follow already queued Good values; SDK Session identity/endpoint changes and transfers discard affected queued values before rechecking. Already sent Republish messages remain.
Exceptions, missing answers, and unknown nodes can preserve a previous decision. A retained grant is not a guarantee of fail-closed revocation. If immediate revocation is required, coordinate application policy mutation, refresh, and delivery. External samplers also need a refresher even while their source is quiet; see Sampling.
User guide for Milo 1.2.0-RC1 at source 69cb25087, 2026-10-12. Home · Getting started · Examples · Troubleshooting · Release notes
- Home
- Start here
- Client guide
- Server guide
- Feature guides
- Reference
- Operations
- Examples
- Release notes
- Contributing