# Secure servers

> How-to guide: Secure the server: endpoints, certificates, users, roles and access control lists, audit events, and a checklist for production.

> This content was machine translated and may contain errors. [Read in German](https://www.traeger.de/docs/development/opcua/security/server)

**Sample applications:**

- Server.Authentication ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Server.Authentication), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Server.Authentication)): This sample demonstrates how to setup an OPC UA server with a UserName policy using the UserName ACL for custom user name and password authentication.
- Server.NodeAccess ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Scenarios/Server.NodeAccess), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Scenarios/Server.NodeAccess)): This use case realizes restricted node access using built-in ACL functionality And subclassing for user dependent node metadata.
- Client.Authentication ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Client.Authentication), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Client.Authentication)): This sample demonstrates how to authenticate at an OPC UA server using user name and password.

This page brings together all mechanisms a server uses to protect itself and its data. The default values of the OPC UA .NET SDK are designed for an easy start and are not suitable for production. The section [Harden for production](https://www.traeger.de/en/docs/development/opcua/security/server#harden-for-production) summarizes which settings you should change for production. The pages [Understand certificates](https://www.traeger.de/en/docs/development/opcua/security/certificates) and [Users and roles](https://www.traeger.de/en/docs/development/opcua/security/users-roles) describe the fundamentals.

## Default values at a glance

Types used: `OpcServer`, `OpcServerSecurity`, `OpcServerBinding`, `OpcCertificateStores`, `OpcAuthenticationLimits`, `OpcOperationLimits`

| Setting | Default value | Recommendation for production |
|---|---|---|
| `Security.EndpointPolicies` | empty: the server offers only endpoints with the mode `None` | only policies with `SignAndEncrypt` and `Basic256Sha256`, `Aes128_Sha256_RsaOaep`, or `Aes256_Sha256_RsaPss` |
| `Binding.Addresses` | empty: the server binds to all IPv4 and IPv6 addresses of the computer, even if its address is `localhost` | restrict to the network interfaces that clients use |
| `Security.AutoAcceptUntrustedCertificates` | `true`: the server accepts unknown client certificates | `false`; add client certificates to the `TrustedPeerStore` in advance |
| `Security.CertificateValidationRules` | `Default`: the server accepts certificates signed with SHA-1, and backward-compatible validation is active | `RejectSHA1Signed` and `DisableBackwardsCompatibleValidation` |
| `Security.AnonymousAcl` | active as long as no other access control list is active | disable; allow known users only |
| `Security.IsAuditingEnabled` | `false`: the server reports no audit events | `true` if needed (see [Audit events](https://www.traeger.de/en/docs/development/opcua/security/server#audit-events)) |
| `CertificateStores.AutoCreateCertificate` | `true`: if no certificate exists, the server creates a new one (up to version 2.58 also if the existing one is invalid) | `false`, and use a managed certificate |
| Automatically created certificate | self-signed, key length 2048 bits, valid for 600 months | certificate from your own certificate authority with a lifetime according to your policy |
| `AuthenticationLimits` | active: after 5 failed attempts within 5 seconds, an increasing delay starting at 1 second, at most 30 seconds | adjust to the number of users |
| `OperationLimits.AutoDetermineLimits` | `false`: the server reports no per-request limits to its clients | `true` |
| `Transport` | `MaxMessageSize` 10 MB, `MaxByteStringLength` 5 MB, `MaxStringLength` and `MaxArrayLength` 65535 each | increase only as far as the application needs |

> **Insecure default values**
> With the default values, a server is reachable through all network interfaces of the computer, communicates without encryption, allows anonymous users, and accepts the certificate of every client it does not know. Anyone who can reach the computer over the network can then read and write data and call methods. Change these settings before you go to production.

## Secure endpoints and the network

Types used: `OpcServer`, `OpcServerSecurity`, `OpcSecurityPolicy`, `OpcServerBinding`

**Offer encrypted endpoints only**: As long as the `EndpointPolicies` property is empty, the server offers only endpoints with the mode `None`. Through these endpoints, it transfers data neither signed nor encrypted. As soon as you add a policy, the endpoint with the mode `None` is no longer offered. Therefore, add only policies with the mode `SignAndEncrypt`, and specify the algorithm explicitly. If you specify none (`OpcSecurityAlgorithm.Auto`), the SDK chooses `Basic256Sha256`. The section [Endpoints](https://www.traeger.de/en/docs/development/opcua/server/configuration#endpoints) describes how the server combines addresses and policies into endpoints.

**Consider the TLS versions for HTTPS**: As of version 2.59, the server lets the operating system choose the TLS versions for endpoints with the `https` scheme. Up to version 2.58, TLS 1.0, 1.1, and 1.2 were fixed. Clients that support only TLS 1.0 or 1.1 may therefore no longer be able to connect over HTTPS.

**Bind only to the network interfaces you need**: The address of the server (for example `opc.tcp://localhost:4840/`) determines the endpoints the server reports to its clients. It does not determine the network interfaces to which the server binds its ports. Unless you specify otherwise, the server binds to all IPv4 and IPv6 addresses of the computer. This makes it reachable from the network even if its address is `localhost`. Restrict the binding to the addresses you need with the `Binding` property (see [Bind to network interfaces](https://www.traeger.de/en/docs/development/opcua/server/configuration#bind-to-network-interfaces)). Exclusive binding (`UseExclusiveAddress`), which is active by default, also prevents another process from using the same port.

## Validate and trust client certificates

Types used: `OpcServer`, `OpcServerSecurity`, `OpcCertificateValidationFailedEventArgs`, `OpcCertificateValidationRules`, `OpcCertificateStores`, `OpcCertificateStoreInfo`

When it connects, a client sends its certificate to the server for authentication. Based on the client certificate, the server decides whether it allows the connection with the client and thus trusts it.

**Accept trusted certificates only**: If the server should accept only trusted certificates, disable the default acceptance of all unknown certificates. The server does not automatically accept expired, revoked, or otherwise invalid certificates, even without this setting:

```csharp
server.Security.AutoAcceptUntrustedCertificates = false;

```

```vbnet
server.Security.AutoAcceptUntrustedCertificates = False

```

**Validate certificates yourself**: Once you have disabled the default acceptance of all certificates, you should provide custom validation of the certificates:

```csharp
server.CertificateValidationFailed += HandleCertificateValidationFailed;
...
private void HandleCertificateValidationFailed(object sender, OpcCertificateValidationFailedEventArgs e)
{
    if (e.Certificate.Thumbprint == "...")
        e.Accept = true;
}

```

```vbnet
AddHandler server.CertificateValidationFailed, AddressOf HandleCertificateValidationFailed
'...
Private Sub HandleCertificateValidationFailed(sender As Object, e As OpcCertificateValidationFailedEventArgs)
    If e.Certificate.Thumbprint = "..." Then
        e.Accept = True
    End If
End Sub

```

**Trust a certificate manually**: If the server has classified a client certificate as untrusted, you can declare it trusted manually. To do this, save it in the `TrustedPeerStore`:

```csharp
// In context of the event handler the sender is an OpcServer.
var server = (OpcServer)sender;

if (!server.CertificateStores.TrustedPeerStore.Contains(e.Certificate))
    server.CertificateStores.TrustedPeerStore.Add(e.Certificate);

```

```vbnet
' In context of the event handler the sender is an OpcServer.
Dim server As OpcServer = CType(sender, OpcServer)

If Not server.CertificateStores.TrustedPeerStore.Contains(e.Certificate) Then
    server.CertificateStores.TrustedPeerStore.Add(e.Certificate)
End If

```

**Stricter rules for certificate validation**: The `CertificateValidationRules` property sets them. By default, the server also accepts certificates signed with SHA-1:

```csharp
server.Security.CertificateValidationRules
        = OpcCertificateValidationRules.RejectSHA1Signed
        | OpcCertificateValidationRules.DisableBackwardsCompatibleValidation;
```

```vbnet
server.Security.CertificateValidationRules _
        = OpcCertificateValidationRules.RejectSHA1Signed _
        Or OpcCertificateValidationRules.DisableBackwardsCompatibleValidation
```

## Protect your own certificate

Types used: `OpcServer`, `OpcCertificateManager`, `OpcCertificateSettings`, `OpcCertificateStores`, `OpcCertificateRequestedEventArgs`

The section [Application certificate](https://www.traeger.de/en/docs/development/opcua/server/configuration#application-certificate) describes how a certificate is loaded, created, and stored. For security, the following points matter:

- **Properties of the certificate**: A certificate created by the SDK is self-signed, uses a 2048-bit key, and is valid for 600 months. You set the key length (`KeySize`), the validity in months (`Lifetime`), and the domains under which the server is reachable (`DomainNames`) with the `OpcCertificateSettings` class. For a server, the SDK adds the fully qualified domain name (FQDN) of the machine to the domains if DNS resolves it. Clients that connect by this name then accept the certificate. As the organization (O) in the subject, the SDK enters the manufacturer from `OpcServerInfo.CompanyName` (property `Organization`). It does not change an existing certificate. For production, use a certificate issued by the certificate authority of your company. Clients then only have to trust the certificate authority.
- **Do not replace a certificate silently**: If the server finds no valid certificate at startup, for example because it has expired, it raises the `CertificateRequested` event. Up to version 2.58, the following applies: If no handler provides a certificate and the `AutoCreateCertificate` property is active, the server deletes the previous certificate from the `ApplicationStore` and creates a new, self-signed certificate. Clients that trusted the previous certificate then reject the server. As of version 2.59, the server never replaces an existing but invalid certificate automatically. The `Reason` property of the event data then states the reason: `Invalid`, `UseNotAllowed`, `Revoked`, `TimeInvalid`, `IssuerUseNotAllowed`, `IssuerRevoked`, or `IssuerTimeInvalid`. For an expired certificate, for example, it is `TimeInvalid`. The `AutoCreateCertificate` property of the event data is `false` in this case. Unless the handler provides another certificate, the server keeps using the existing certificate. As of version 2.59, the server uses a certificate that the handler provides through the `AlternativeCertificate` property at startup. The certificate must contain its private key. Provide a certificate yourself through the `CertificateRequested` event and disable automatic creation.
- **Protect the private key**: The `ApplicationStore` contains the certificate including its private key. The SDK does not set access permissions for the files of the certificate stores. Place the certificate stores in a directory that only the user account of the server can access. Load an existing certificate from a password-protected `.pfx` file.

## Authenticate users

Types used: `OpcServer`, `OpcUserIdentity`, `OpcServerIdentity`, `OpcCertificateIdentity`, `OpcServerSecurity`, `OpcAccessControlList`, `OpcAnonymousAcl`, `OpcUserNameAcl`, `OpcCertificateAcl`, `OpcAccessControlEntry`, `OpcOperationType`, `OpcRequestType`, `OpcAccessControlMode`

By default, a server also allows access without a specific user identity. This kind of user authentication is called anonymous authentication. If a client specifies a user identity, however, the server must know this identity. Only then does it allow access with this identity. If users should identify themselves, for example with a user name and password or with a certificate, configure and enable the corresponding access control lists (ACL). Configuring the access control lists includes configuring the access control entries (ACE). Each entry represents a principal with a specific identity, that is, a user name and password or a certificate. You add the entries to the lists.

The access control list for anonymous users is active only as long as no other access control list is active. For example, if you enable the access control list for user names and passwords, anonymous access is switched off automatically. If it should still be possible, explicitly set the `IsEnabled` property of the `AnonymousAcl` to `true`. Conversely, the following example explicitly switches off anonymous access:

**Disable the anonymous access control list**:

```csharp
server.Security.AnonymousAcl.IsEnabled = false;

```

```vbnet
server.Security.AnonymousAcl.IsEnabled = False

```

**Configure the access control list for user names and passwords**:

```csharp
var acl = server.Security.UserNameAcl;

acl.AddEntry("username1", "password1");
acl.AddEntry("username2", "password2");
acl.AddEntry("username3", "password3");
...
acl.IsEnabled = true;

```

```vbnet
Dim acl = server.Security.UserNameAcl

acl.AddEntry("username1", "password1")
acl.AddEntry("username2", "password2")
acl.AddEntry("username3", "password3")
'...
acl.IsEnabled = True

```

**Configure the access control list for certificates**:

```csharp
var acl = server.Security.CertificateAcl;
  
acl.AddEntry(new X509Certificate2(@".\user1.der"));
acl.AddEntry(new X509Certificate2(@".\user2.der"));
acl.AddEntry(new X509Certificate2(@".\user3.der"));
...
acl.IsEnabled = true;

```

```vbnet
Dim acl = server.Security.CertificateAcl

acl.AddEntry(New X509Certificate2(".\user1.der"))
acl.AddEntry(New X509Certificate2(".\user2.der"))
acl.AddEntry(New X509Certificate2(".\user3.der"))
'...
acl.IsEnabled = True

```

If a custom `X509CertificateValidator` is set through the `CertificateValidator` property of the `CertificateAcl`, the server additionally validates the certificate of a user identity before it compares the thumbprint.

All access control lists provided by the SDK use the access control mode `Whitelist`. In this mode, every entry has access to all types of requests simply because it is defined. This also applies if access to them was not explicitly allowed. Therefore, deny the entries all operations that are not permitted. You set the allowed and denied operations directly on the entry that the access control list returns when you add it. The sections [Access control modes](https://www.traeger.de/en/docs/development/opcua/security/server#access-control-modes), [Roles](https://www.traeger.de/en/docs/development/opcua/security/server#roles), and [Custom access control lists](https://www.traeger.de/en/docs/development/opcua/security/server#custom-access-control-lists) describe other modes, roles, and custom access control lists.

**1.** Keep a reference to an access control entry:

```csharp
var user1 = acl.AddEntry("username1", "password1");

```

```vbnet
Dim user1 = acl.AddEntry("username1", "password1")

```

**2.** Deny the access control entry two rights:

```csharp
user1.Deny(OpcRequestType.Write);
user1.Deny(OpcRequestType.HistoryUpdate);

```

```vbnet
user1.Deny(OpcRequestType.Write)
user1.Deny(OpcRequestType.HistoryUpdate)

```

**3.** Allow a previously denied right again:

```csharp
user1.Allow(OpcRequestType.HistoryUpdate);

```

```vbnet
user1.Allow(OpcRequestType.HistoryUpdate)

```

### Store passwords securely

Types used: `OpcServerIdentity`, `OpcUserNameAcl`

If you add a user with a password as a string (`AddEntry(userName, password)`), the server keeps the password in memory in plain text. If the passwords of your users already exist as hash values, derive from the `OpcServerIdentity` class. The constructor takes the stored hash value as a byte array. The overridden `TransformPassword` method computes the hash value of the password sent at logon with the same algorithm. The server compares the two values in constant time. The following example uses PBKDF2 with a salt per user:

```csharp
public sealed class HashedServerIdentity : OpcServerIdentity
{
    private const int Iterations = 100000;
    private readonly byte[] salt;
 
    public HashedServerIdentity(string userName, byte[] passwordHash, byte[] salt)
        : base(userName, password: passwordHash)
    {
        this.salt = salt;
    }
 
    public static byte[] Hash(string password, byte[] salt)
    {
        using (var pbkdf2 = new Rfc2898DeriveBytes(
                password,
                salt,
                Iterations,
                HashAlgorithmName.SHA256)) {
            return pbkdf2.GetBytes(32);
        }
    }
 
    protected override byte[] TransformPassword(string password)
    {
        return Hash(password, this.salt);
    }
}
```

```vbnet
Public NotInheritable Class HashedServerIdentity
    Inherits OpcServerIdentity

    Private Const Iterations As Integer = 100000
    Private ReadOnly salt As Byte()

    Public Sub New(ByVal userName As String, ByVal passwordHash As Byte(), ByVal salt As Byte())
        MyBase.New(userName, password:=passwordHash)
        Me.salt = salt
    End Sub

    Public Shared Function Hash(ByVal password As String, ByVal salt As Byte()) As Byte()
        Using pbkdf2 = New Rfc2898DeriveBytes( _
                password, _
                salt, _
                Iterations, _
                HashAlgorithmName.SHA256)
            Return pbkdf2.GetBytes(32)
        End Using
    End Function

    Protected Overrides Function TransformPassword(ByVal password As String) As Byte()
        Return Hash(password, Me.salt)
    End Function
End Class

```

To add the identity:

```csharp
// Hash and salt come from the user store of the application.
var identity = new HashedServerIdentity("operator", storedHash, storedSalt);
 
server.Security.UserNameAcl.AddEntry(identity);
server.Security.UserNameAcl.IsEnabled = true;
```

```vbnet
'Hash and salt come from the user store of the application.
Dim identity = New HashedServerIdentity("operator", storedHash, storedSalt)

server.Security.UserNameAcl.AddEntry(identity)
server.Security.UserNameAcl.IsEnabled = True
```

### Validate user certificates

Types used: `OpcCertificateAcl`

If a user logs on with a certificate, the server compares its thumbprint with the entries of the `CertificateAcl`. If the server should validate the certificate first, for example against the certificate authority of your company and for revocation, set a custom `X509CertificateValidator` with the `CertificateValidator` property (namespace `System.IdentityModel.Selectors`). If the validator rejects the certificate with an exception, the server denies the logon:

```csharp
public sealed class UserCertificateValidator : X509CertificateValidator
{
    public override void Validate(X509Certificate2 certificate)
    {
        using (var chain = new X509Chain()) {
            chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
 
            if (!chain.Build(certificate))
                throw new SecurityException("The user certificate is not trusted.");
        }
    }
}

```

```vbnet
Public NotInheritable Class UserCertificateValidator
    Inherits X509CertificateValidator

    Public Overrides Sub Validate(ByVal certificate As X509Certificate2)
        Using chain = New X509Chain()
            chain.ChainPolicy.RevocationMode = X509RevocationMode.Online

            If Not chain.Build(certificate) Then
                Throw New SecurityException("The user certificate is not trusted.")
            End If
        End Using
    End Sub
End Class
```

```csharp
server.Security.CertificateAcl.CertificateValidator = new UserCertificateValidator();
server.Security.CertificateAcl.AddEntry(userCertificate);
server.Security.CertificateAcl.IsEnabled = true;
```

```vbnet
server.Security.CertificateAcl.CertificateValidator = New UserCertificateValidator()
server.Security.CertificateAcl.AddEntry(userCertificate)
server.Security.CertificateAcl.IsEnabled = True
```

### Restrict users to endpoints

Types used: `OpcAccessControlEntry`, `OpcEndpointIdentity`, `OpcAccessControlMode`

Besides operations, an access control entry can also define the endpoints through which a user may log on. The access control mode for endpoints determines how the server evaluates allowed (`Enable`) and denied (`Disable`) endpoints. You specify it as the second mode when you add an entry. In the `Blacklist` mode, the user may log on only through explicitly allowed endpoints. Through all other endpoints, the server denies the logon with the status `BadUserAccessDenied`. This lets you restrict a service account to an endpoint with the mode `SignAndEncrypt`, for example. The address and policy of the `OpcEndpointIdentity` must match the endpoint through which the client connects:

```csharp
var endpoint = new OpcEndpointIdentity(
        "opc.tcp://192.168.0.10:4840/",
        new OpcSecurityPolicy(OpcSecurityMode.SignAndEncrypt, OpcSecurityAlgorithm.Basic256Sha256));
 
// Endpoint access mode Blacklist: only explicitly enabled endpoints are permitted.
var service = server.Security.UserNameAcl.AddEntry(
        "service",
        servicePassword,
        operationAccessMode: OpcAccessControlMode.Whitelist,
        endpointAccessMode: OpcAccessControlMode.Blacklist);
 
service.Enable(endpoint);
```

```vbnet
Dim endpoint = New OpcEndpointIdentity( _
        "opc.tcp://192.168.0.10:4840/", _
        New OpcSecurityPolicy(OpcSecurityMode.SignAndEncrypt, OpcSecurityAlgorithm.Basic256Sha256))

'Endpoint access mode Blacklist: only explicitly enabled endpoints are permitted.
Dim service = server.Security.UserNameAcl.AddEntry( _
        "service", _
        servicePassword, _
        operationAccessMode:=OpcAccessControlMode.Whitelist, _
        endpointAccessMode:=OpcAccessControlMode.Blacklist)

service.Enable(endpoint)
```

### Logon with tokens

For user identities with issued tokens, the `IssuedTokenAcl` validates Kerberos tickets only, and only if the server runs on .NET Framework. On .NET Standard and .NET (Core), the server rejects such logons.

## Roles

Types used: `OpcServerSecurity`, `OpcServerSecurityRoles`, `OpcAccessControlRole`, `OpcAccessControlEntry`, `OpcPrincipal`

OPC UA defines a set of standard roles to which the users of a server can belong. The `Roles` property of the `OpcServerSecurity` class provides these roles as instances of the `OpcAccessControlRole` class:

| Role | Meaning |
|---|---|
| `Anonymous` | May browse and read non-security-related nodes in the Server object and all type nodes. |
| `AuthenticatedUser` | May browse and read non-security-related nodes. |
| `Observer` | May browse, read current and historical data and events, and subscribe to data and events. |
| `Operator` | Like `Observer`, and may also write some current data and call some methods. |
| `Engineer` | May browse, read and write configuration data, read historical data and events, call methods, and subscribe. |
| `Supervisor` | May browse, read current and historical data and events, call methods, and subscribe. |
| `ConfigureAdmin` | May change non-security-related configuration settings. |
| `SecurityAdmin` | May change security-related settings, for example the certificate of the server (see [Manage certificates with GDS](https://www.traeger.de/en/docs/development/opcua/security/gds-push)). |

The server automatically assigns anonymous users to the `Anonymous` role and all users logged on through an access control list to the `AuthenticatedUser` role. You assign further roles to an access control entry with the `Assign` method:

```csharp
var acl = server.Security.UserNameAcl;
 
var operator1 = acl.AddEntry("operator1", password: "password1");
operator1.Assign(server.Security.Roles.Operator);
 
var admin = acl.AddEntry("admin", password: "password2");
admin.Assign(server.Security.Roles.ConfigureAdmin, server.Security.Roles.SecurityAdmin);
 
acl.IsEnabled = true;
```

```vbnet
Dim acl = server.Security.UserNameAcl

Dim operator1 = acl.AddEntry("operator1", password:="password1")
operator1.Assign(server.Security.Roles.Operator)

Dim admin = acl.AddEntry("admin", password:="password2")
admin.Assign(server.Security.Roles.ConfigureAdmin, server.Security.Roles.SecurityAdmin)

acl.IsEnabled = True
```

The `Revoke` method removes the assignment again:

```csharp
admin.Revoke(server.Security.Roles.ConfigureAdmin);
```

```vbnet
admin.Revoke(server.Security.Roles.ConfigureAdmin)
```

To find out whether a principal belongs to a role, use the `IsInRole` method of the `OpcPrincipal` class or the `Contains` method of the respective role. The `AccessControlRoles` property of the `OpcServerSecurity` class returns the roles the server uses.

## Access control modes

Types used: `OpcAccessControlList`, `OpcAccessControlEntry`, `OpcAccessControlMode`, `OpcRequestType`

The access control mode of an entry (`OpcAccessControlMode` enumeration) determines how the server evaluates the operations allowed (`Allow`) and denied (`Deny`) for the entry:

- **Whitelist** (default of the access control lists of the SDK): All operations are allowed unless they were explicitly denied for the entry.
- **Blacklist**: All operations are denied unless they were explicitly allowed for the entry.
- **Explicit**: Only what was explicitly allowed or denied for the entry counts. The server denies only explicitly denied operations.

You set the mode when you add an entry:

```csharp
var guest = acl.AddEntry(
        "guest",
        password: "password3",
        operationAccessMode: OpcAccessControlMode.Blacklist);
 
// In blacklist mode only explicitly allowed operations are permitted.
guest.Allow(OpcRequestType.Browse, OpcRequestType.Read);
```

```vbnet
Dim guest = acl.AddEntry( _
        "guest", _
        password:="password3", _
        operationAccessMode:=OpcAccessControlMode.Blacklist)

'In blacklist mode only explicitly allowed operations are permitted.
guest.Allow(OpcRequestType.Browse, OpcRequestType.Read)
```

## Custom access control lists

Types used: `OpcServerSecurity`, `OpcAccessControlList`, `OpcAccessControlTokenType`, `OpcIdentityToken`, `OpcUserNameIdentityToken`, `OpcPrincipal`, `OpcServerIdentity`

If you do not want to maintain the users of a server in its code, but in an existing user management system, for example, implement a custom access control list. To do this, derive from the `OpcAccessControlList` class, set the kind of supported identities with the `TokenType` property, and implement the `Authenticate` method. If the method returns a principal, the user is logged on. If it returns `null`, the server checks the next active access control list. As with the access control lists of the SDK, assign logged-on users to the `AuthenticatedUser` role:

```csharp
public class MyUserNameAcl : OpcAccessControlList
{
    private readonly Dictionary<string, OpcPrincipal> principals
            = new Dictionary<string, OpcPrincipal>();
 
    public MyUserNameAcl(OpcServerBase owner)
        : base(
                owner,
                operationAccessMode: OpcAccessControlMode.Whitelist,
                endpointAccessMode: OpcAccessControlMode.Whitelist)
    {
    }
 
    protected override OpcAccessControlTokenType TokenType
    {
        get { return OpcAccessControlTokenType.UserName; }
    }
 
    public override OpcPrincipal Authenticate(OpcIdentityToken identityToken)
    {
        var token = identityToken as OpcUserNameIdentityToken;
 
        // Verify the user name and password, e.g. using an existing user management.
        if (token == null || !UserStore.Verify(token.UserName, token.DecryptedPassword))
            return null;
 
        lock (this.principals) {
            OpcPrincipal principal;
 
            if (!this.principals.TryGetValue(token.UserName, out principal)) {
                principal = new OpcPrincipal(new OpcServerIdentity(token.UserName));
                this.AddEntry(principal);
 
                // Authenticated users are expected to be a member of the AuthenticatedUser role.
                this.Owner.Security.Roles.AuthenticatedUser.Assign(principal);
                this.principals.Add(token.UserName, principal);
            }
 
            return principal;
        }
    }
}
```

```vbnet
Public Class MyUserNameAcl
    Inherits OpcAccessControlList

    Private ReadOnly principals As New Dictionary(Of String, OpcPrincipal)()

    Public Sub New(ByVal owner As OpcServerBase)
        MyBase.New( _
                owner, _
                operationAccessMode:=OpcAccessControlMode.Whitelist, _
                endpointAccessMode:=OpcAccessControlMode.Whitelist)
    End Sub

    Protected Overrides ReadOnly Property TokenType As OpcAccessControlTokenType
        Get
            Return OpcAccessControlTokenType.UserName
        End Get
    End Property

    Public Overrides Function Authenticate(ByVal identityToken As OpcIdentityToken) As OpcPrincipal
        Dim token = TryCast(identityToken, OpcUserNameIdentityToken)

        'Verify the user name and password, e.g. using an existing user management.
        If token Is Nothing OrElse Not UserStore.Verify(token.UserName, token.DecryptedPassword) Then
            Return Nothing
        End If

        SyncLock Me.principals
            Dim principal As OpcPrincipal = Nothing

            If Not Me.principals.TryGetValue(token.UserName, principal) Then
                principal = New OpcPrincipal(New OpcServerIdentity(token.UserName))
                Me.AddEntry(principal)

                'Authenticated users are expected to be a member of the AuthenticatedUser role.
                Me.Owner.Security.Roles.AuthenticatedUser.Assign(principal)
                Me.principals.Add(token.UserName, principal)
            End If

            Return principal
        End SyncLock
    End Function
End Class
```

The `AddAccessControlList` method registers the custom access control list with the server and enables it. The `RemoveAccessControlList` method removes it again:

```csharp
var customAcl = new MyUserNameAcl(server);
server.Security.AddAccessControlList(customAcl);
 
customAcl.IsEnabled = true;
```

```vbnet
Dim customAcl = New MyUserNameAcl(server)
server.Security.AddAccessControlList(customAcl)

customAcl.IsEnabled = True
```

As with the access control lists of the SDK, enabling a custom access control list switches off anonymous access automatically (see [Authenticate users](https://www.traeger.de/en/docs/development/opcua/security/server#authenticate-users)).

## Limit logon attempts

Types used: `OpcServer`, `OpcAuthenticationLimits`

To protect against brute-force attacks on passwords, the server limits logon attempts through the `AuthenticationLimits` property. It counts the attempts per user and client IP address. If more than `MaxFailedAttempts` (5 by default) logon attempts fail within the `DelayWindow` time window (5 seconds by default), the server delays every further attempt. It first delays by `Delay` (1 second by default) and doubles the delay with each further failed attempt, up to `MaxDelay` (30 seconds by default). A successful logon resets the counter:

```csharp
server.AuthenticationLimits.MaxFailedAttempts = 3;                  // Default: 5
server.AuthenticationLimits.DelayWindow = TimeSpan.FromSeconds(10); // Default: 5 seconds
server.AuthenticationLimits.Delay = TimeSpan.FromSeconds(2);        // Default: 1 second
server.AuthenticationLimits.MaxDelay = TimeSpan.FromMinutes(1);     // Default: 30 seconds
```

```vbnet
server.AuthenticationLimits.MaxFailedAttempts = 3                  'Default: 5
server.AuthenticationLimits.DelayWindow = TimeSpan.FromSeconds(10) 'Default: 5 seconds
server.AuthenticationLimits.Delay = TimeSpan.FromSeconds(2)        'Default: 1 second
server.AuthenticationLimits.MaxDelay = TimeSpan.FromMinutes(1)     'Default: 30 seconds
```

The server also limits the number of concurrent logon attempts per identity (`MaxConcurrentAttemptsPerIdentity`, 3 by default) and in total (`MaxConcurrentAttempts`, 32 by default). It rejects attempts beyond these limits with the status `BadUserAccessDenied`. If the server should determine these limits from the processor cores and the available memory, enable the `AutoDetermineLimits` property:

```csharp
// Default: false
server.AuthenticationLimits.AutoDetermineLimits = true;
```

```vbnet
'Default: False
server.AuthenticationLimits.AutoDetermineLimits = True
```

For connections over the `https` scheme, the server has no information about the remote peer. The limit therefore does not apply there.

The `SessionActivationFailed` event reports rejected logons (see [Audit events](https://www.traeger.de/en/docs/development/opcua/security/server#audit-events)).

## Protect against overload

Types used: `OpcServer`, `OpcOperationLimits`, `OpcTransport`, `OpcRequestValidatingEventArgs`

Clients can put load on a server with very large or very many requests. Three mechanisms limit this:

- **Limits per request**: By default, the server reports no limits to its clients on how many nodes they may read, write, browse, or subscribe to per request. With `OperationLimits.AutoDetermineLimits`, the server determines suitable limits from the available system resources and reports them to its clients (see [Data transfer limits](https://www.traeger.de/en/docs/development/opcua/server/configuration#data-transfer-limits)).
- **Message limits**: The limits under `Transport` restrict the size of messages, strings, and arrays. Increase them only as far as the application requires.
- **Reject requests**: With the `RequestValidating` event, the server can reject requests before processing them, for example all requests that change the structure of the address space (see [Validate requests](https://www.traeger.de/en/docs/development/opcua/server/request-monitoring#validate-requests)):

```csharp
server.RequestValidating += HandleRequestValidating;
...
private static void HandleRequestValidating(
        object sender,
        OpcRequestValidatingEventArgs e)
{
    // Clients must not change the structure of the address space.
    if (e.RequestType == OpcRequestType.AddNodes
            || e.RequestType == OpcRequestType.DeleteNodes
            || e.RequestType == OpcRequestType.AddReferences
            || e.RequestType == OpcRequestType.DeleteReferences) {
        e.Cancel = true;
    }
}
```

```vbnet
AddHandler server.RequestValidating, AddressOf HandleRequestValidating
...
Private Shared Sub HandleRequestValidating( _
        ByVal sender As Object, _
        ByVal e As OpcRequestValidatingEventArgs)
    'Clients must not change the structure of the address space.
    If e.RequestType = OpcRequestType.AddNodes _
            OrElse e.RequestType = OpcRequestType.DeleteNodes _
            OrElse e.RequestType = OpcRequestType.AddReferences _
            OrElse e.RequestType = OpcRequestType.DeleteReferences Then
        e.Cancel = True
    End If
End Sub

```

## Manage certificates with GDS

A Global Discovery Server (GDS) can manage the application certificate and the trust list of the server remotely (GDS Push Management). The page [Manage certificates with GDS](https://www.traeger.de/en/docs/development/opcua/security/gds-push) describes the prerequisites, procedure, and limitations.

## Audit events

Types used: `OpcServer`, `OpcServerSecurity`, `OpcSession`, `OpcRequestValidatingEventArgs`, `OpcSessionActivationFailedEventArgs`

Audit events (according to OPC UA Part 4) record security-related actions. The server reports them only if the `Security.IsAuditingEnabled` property is `true` (`false` by default). You can also switch the property at runtime; it then takes effect at once:

```csharp
server.Security.IsAuditingEnabled = true;
```

```vbnet
server.Security.IsAuditingEnabled = True
```

The server reports the following audit events through the Server object:

| Event type | Raised |
|---|---|
| AuditOpenSecureChannelEventType | when a client opens a secure channel; also when the server rejects opening or renewing a secure channel |
| AuditChannelEventType | when a client closes a secure channel with a CloseSecureChannel request |
| AuditCreateSessionEventType | when a client creates a session |
| AuditActivateSessionEventType | when a client activates a session, that is, logs on |

The server reports the events about secure channels only for connections over `opc.tcp`. As defined by OPC UA, a client receives the audit events only if it is connected through an endpoint with the mode `SignAndEncrypt` or through HTTPS and its user belongs to the `SecurityAdmin` role (see [Roles](https://www.traeger.de/en/docs/development/opcua/security/server#roles)).

The server does not report further audit events, such as for writes, method calls, or certificate changes. An application cannot report its own audit events. The section [Receive audit events](https://www.traeger.de/en/docs/development/opcua/client/events#receive-audit-events) describes how a client receives the events.

If your application needs a log of security-related actions, keep it yourself with the events of the server, for example `SessionActivated` for logons, `SessionActivationFailed` for rejected logons, and `RequestValidating` for writes and method calls (see [Monitor requests](https://www.traeger.de/en/docs/development/opcua/server/request-monitoring)):

```csharp
server.SessionActivated += HandleSessionActivated;
server.RequestValidating += HandleCriticalRequest;
...
private static void HandleSessionActivated(object sender, OpcSessionEventArgs e)
{
    Console.WriteLine(
            "{0:u} session {1}: {2}",
            DateTime.UtcNow,
            e.Session.Name,
            e.Session.UsedIdentity);
}
 
private static void HandleCriticalRequest(object sender, OpcRequestValidatingEventArgs e)
{
    if (e.RequestType == OpcRequestType.Write || e.RequestType == OpcRequestType.Call) {
        Console.WriteLine(
                "{0:u} {1} in session {2}",
                DateTime.UtcNow,
                e.RequestType,
                e.Context.SessionId);
    }
}
```

```vbnet
AddHandler server.SessionActivated, AddressOf HandleSessionActivated
AddHandler server.RequestValidating, AddressOf HandleCriticalRequest
...
Private Shared Sub HandleSessionActivated(ByVal sender As Object, ByVal e As OpcSessionEventArgs)
    Console.WriteLine( _
            "{0:u} session {1}: {2}", _
            DateTime.UtcNow, _
            e.Session.Name, _
            e.Session.UsedIdentity)
End Sub

Private Shared Sub HandleCriticalRequest( _
        ByVal sender As Object, _
        ByVal e As OpcRequestValidatingEventArgs)
    If e.RequestType = OpcRequestType.Write OrElse e.RequestType = OpcRequestType.Call Then
        Console.WriteLine( _
                "{0:u} {1} in session {2}", _
                DateTime.UtcNow, _
                e.RequestType, _
                e.Context.SessionId)
    End If
End Sub
```

`SessionActivationFailed` occurs for each rejected logon. The `Status` property states the reason, and the `Identity` property states the identity the client tried to log on with, without its password or token data. For a user name, it is an `OpcUserNameIdentityTokenInfo` with the name of the user (`UserName`); for a user certificate, it is an `OpcCertificateIdentityTokenInfo` with the certificate (`Certificate`):

```csharp
server.SessionActivationFailed += HandleSessionActivationFailed;
...
private static void HandleSessionActivationFailed(
        object sender,
        OpcSessionActivationFailedEventArgs e)
{
    var userName = e.Identity as OpcUserNameIdentityTokenInfo;
 
    Console.WriteLine(
            "{0:u} logon rejected ({1}): {2}",
            DateTime.UtcNow,
            e.Status.Code,
            userName != null ? userName.UserName : "no user name");
}
```

```vbnet
AddHandler server.SessionActivationFailed, AddressOf HandleSessionActivationFailed
...
Private Shared Sub HandleSessionActivationFailed( _
        ByVal sender As Object, _
        ByVal e As OpcSessionActivationFailedEventArgs)
    Dim userName = TryCast(e.Identity, OpcUserNameIdentityTokenInfo)

    Console.WriteLine( _
            "{0:u} logon rejected ({1}): {2}", _
            DateTime.UtcNow, _
            e.Status.Code, _
            If(userName IsNot Nothing, userName.UserName, "no user name"))
End Sub

```

## Harden for production

Check the following points before you go to production:

1. **Offer encrypted endpoints only**: add only policies with `SignAndEncrypt` and a current algorithm (see [Secure endpoints and the network](https://www.traeger.de/en/docs/development/opcua/security/server#secure-endpoints-and-the-network)).
2. **Restrict network interfaces**: limit the `Binding` property to the addresses you need.
3. **Reject unknown client certificates**: disable `AutoAcceptUntrustedCertificates` and add client certificates to the `TrustedPeerStore` (see [Validate and trust client certificates](https://www.traeger.de/en/docs/development/opcua/security/server#validate-and-trust-client-certificates)).
4. **Validate certificates strictly**: set `CertificateValidationRules` to `RejectSHA1Signed` and `DisableBackwardsCompatibleValidation`.
5. **Manage your own certificate**: use a certificate with an appropriate key length and lifetime, disable `AutoCreateCertificate`, and place the certificate stores in a protected directory (see [Protect your own certificate](https://www.traeger.de/en/docs/development/opcua/security/server#protect-your-own-certificate)).
6. **Switch off anonymous access and manage users**: allow known users only, store passwords as hash values, and validate user certificates (see [Authenticate users](https://www.traeger.de/en/docs/development/opcua/security/server#authenticate-users)).
7. **Keep rights to a minimum**: allow each user only the operations they need, and assign roles deliberately (see [Roles](https://www.traeger.de/en/docs/development/opcua/security/server#roles) and [Access control modes](https://www.traeger.de/en/docs/development/opcua/security/server#access-control-modes)).
8. **Limit logon attempts**: adjust the `AuthenticationLimits` to the number of users (see [Limit logon attempts](https://www.traeger.de/en/docs/development/opcua/security/server#limit-logon-attempts)).
9. **Prevent overload**: enable `OperationLimits.AutoDetermineLimits`, do not increase the limits under `Transport` unnecessarily, and reject unwanted requests (see [Protect against overload](https://www.traeger.de/en/docs/development/opcua/security/server#protect-against-overload)).
10. **Enable GDS Push deliberately**: assign the `SecurityAdmin` role only to one identity with a certificate (see [Manage certificates with GDS](https://www.traeger.de/en/docs/development/opcua/security/gds-push)).
11. **Log security-related actions**: turn on `IsAuditingEnabled` if needed, log logons, rejected logons, and critical requests yourself (see [Audit events](https://www.traeger.de/en/docs/development/opcua/security/server#audit-events)), and monitor the metrics of the server (see [Diagnostics](https://www.traeger.de/en/docs/development/opcua/server/request-monitoring#diagnostics)).
12. **Protect the license key**: do not store the key in plain text in publicly accessible files or repositories (see [Negligent disclosure](https://www.traeger.de/en/docs/development/opcua/license#negligent-disclosure)).

In summary, a server configured this way looks like this:

```csharp
var server = new OpcServer("opc.tcp://192.168.0.10:4840/");
 
// Only encrypted endpoints with current algorithms (removes the implicit None endpoint).
server.Security.EndpointPolicies.Add(new OpcSecurityPolicy(
        OpcSecurityMode.SignAndEncrypt,
        OpcSecurityAlgorithm.Basic256Sha256));
server.Security.EndpointPolicies.Add(new OpcSecurityPolicy(
        OpcSecurityMode.SignAndEncrypt,
        OpcSecurityAlgorithm.Aes256_Sha256_RsaPss));
 
// Listen only on the network interface that clients use.
server.Binding.Addresses.Add(IPAddress.Parse("192.168.0.10"));
 
// Trust only known client certificates and check them strictly.
server.Security.AutoAcceptUntrustedCertificates = false;
server.Security.CertificateValidationRules
        = OpcCertificateValidationRules.RejectSHA1Signed
        | OpcCertificateValidationRules.DisableBackwardsCompatibleValidation;
 
// No anonymous access, only known users.
server.Security.UserNameAcl.AddEntry("operator", operatorPassword);
server.Security.UserNameAcl.IsEnabled = true;
server.Security.AnonymousAcl.IsEnabled = false;
 
// Report operation limits to the clients.
server.OperationLimits.AutoDetermineLimits = true;
 
// Use a managed certificate instead of a generated one.
server.CertificateStores.AutoCreateCertificate = false;
server.Certificate = OpcCertificateManager.LoadCertificate(certificatePath, certificatePassword);
 
server.Start();
```

```vbnet
Dim server = New OpcServer("opc.tcp://192.168.0.10:4840/")

'Only encrypted endpoints with current algorithms (removes the implicit None endpoint).
server.Security.EndpointPolicies.Add(New OpcSecurityPolicy( _
        OpcSecurityMode.SignAndEncrypt, _
        OpcSecurityAlgorithm.Basic256Sha256))
server.Security.EndpointPolicies.Add(New OpcSecurityPolicy( _
        OpcSecurityMode.SignAndEncrypt, _
        OpcSecurityAlgorithm.Aes256_Sha256_RsaPss))

'Listen only on the network interface that clients use.
server.Binding.Addresses.Add(IPAddress.Parse("192.168.0.10"))

'Trust only known client certificates and check them strictly.
server.Security.AutoAcceptUntrustedCertificates = False
server.Security.CertificateValidationRules _
        = OpcCertificateValidationRules.RejectSHA1Signed _
        Or OpcCertificateValidationRules.DisableBackwardsCompatibleValidation

'No anonymous access, only known users.
server.Security.UserNameAcl.AddEntry("operator", operatorPassword)
server.Security.UserNameAcl.IsEnabled = True
server.Security.AnonymousAcl.IsEnabled = False

'Report operation limits to the clients.
server.OperationLimits.AutoDetermineLimits = True

'Use a managed certificate instead of a generated one.
server.CertificateStores.AutoCreateCertificate = False
server.Certificate = OpcCertificateManager.LoadCertificate(certificatePath, certificatePassword)

server.Start()
```
