Server side authentication

One of the tasks of the OPCUA server stack is to manage sessions, including their timeouts and the processing of user tokens. The server tracks the lifetime of sessions and checks if requests are being performed under a valid session.

During session activation, the server receives a user token, decrypts it, and checks its validity. The user represented by the token is not validated, leaving the validation process up to the developer.

Identity token policies

To support user authentication, it is necessary to configure user identity token policies. The server returns to the client the parameters of supported identity token policies. Using this information, the client can create a user token with the required parameters.

The user identity policies on the server are specified in the userIdentityTokens field:

-- Function for validating user credentials sent by client
-- inside token
local function authenticate(tokenType, token, user)

  if tokenType == "anonymous" then
    return true
  elseif tokenType == "username" then
    return user == "admin" and token == "12345"
  elseif tokenType == "x509" then
    local cert = ba.parsecert(token)
    if cert.subject.commonname ~= "admin" then
      return false
    end

    return true
  end

  return false
end


local config = {
  endpoints = {
    {
      endpointUrl = "opc.tcp://localhost:4845",
    }
  },

  io = _G.io,
  certificate = mako.cfgdir.."/../certs/client.pem",
  key =         mako.cfgdir.."/../certs/client.key",

  securePolicies = {
    { -- #1
      securityPolicyUri = ua.Types.SecurityPolicy.None,
    },
    { -- #2
      securityPolicyUri = ua.Types.SecurityPolicy.Basic128Rsa15,
      securityMode = ua.Types.MessageSecurityMode.SignAndEncrypt,
    }
  },

  -- Parameters of user tokens
  userIdentityTokens = {
    {
      policyId = "anonymous",
      tokenType = ua.Types.UserTokenType.Anonymous
    },
    {
      policyId = "username",
      tokenType = ua.Types.UserTokenType.UserName,
      securityPolicyUri = ua.Types.SecurityPolicy.None
    },

Full source

Authentication callback

When the server receives a user token from the client, it first validates its consistency and checks its security parameters. Then, it is required to check the content of the token, such as the username and password, certificate validity, JWT signatures, roles, etc. To perform all of these checks, the server can call an authentication callback function. This callback is specified in the server configuration file in the authenticate field.

The following example demonstrates an implementation of such a function:

local function authenticate(tokenType, token, user)

  if tokenType == "anonymous" then
    return true
  elseif tokenType == "username" then
    return user == "admin" and token == "12345"
  elseif tokenType == "x509" then
    local cert = ba.parsecert(token)
    if cert.subject.commonname ~= "admin" then
      return false
    end

    return true
  end

  return false
end

Full source

The authentication callback function receives a token, validates the token, and must return a boolean value:

  • true - indicates that the server allows access

  • false - indicates that the server rejects access.

The authentication callback is called with specific parameters for each type of token. The following sections describe the parameters for each supported token type.

Anonymous token

To use anonymous (or absent) authentication, you need to add the following element to the userIdentityTokens configuration table:

local userIdentityTokens = {
  {
    policyId = "anonymous",
    tokenType = ua.Types.UserTokenType.Anonymous
  }
}

Full source

The authentication callback is called with only the tokenType equal to “anonymous”. The example:

local function authenticate(tokenType)

  if tokenType == "anonymous" then
    return true
  end

  return false
end

Full source

User Name and password

Configuration file should contain a section with username token policy:

local userIdentityTokens = {
  {
    policyId = "username",
    tokenType = ua.Types.UserTokenType.UserName,
    securityPolicyUri = ua.Types.SecurityPolicy.None
  },
  {
    policyId = "username_basic128Rsa15",
    tokenType = ua.Types.UserTokenType.UserName,
    securityPolicyUri = ua.Types.SecurityPolicy.Basic128Rsa15
  },
}

Full source

If a token policy has a security policy, then the corresponding security policy must be configured in the securityPolicies field, as it contains the server certificate and key that are used for encrypting passwords. It’s important to configure the security policies correctly in order to ensure secure user authentication.

The authentication callback is called with three parameters:

  • tokenType is equal to “username”.

  • token with password.

  • userName with user name.

local function authenticate(tokenType, token, user)

  if tokenType == "username" then
    return user == "admin" and token == "12345"
  end

  return false
end

Full source

X509 user certificate

To enable certificate-based user authentication, the configuration file should include a section with the Certificate user identity policy. This allows clients to provide their certificate to the server for authentication:

local userIdentityTokens = {
  {
    policyId = "x509",
    tokenType = ua.Types.UserTokenType.Certificate,
  },
}

Full source

The authentication callback is called with two parameters:

  • tokenType is equal to “x509”.

  • token with certificate in der format.

local function authenticate(tokenType, token)

  if tokenType == "x509" then
    local cert = ba.parsecert(token)
    return cert.subject.commonname == "admin"
  end

  return false
end

Full source

Issued tokens

The OPCUA server can support authentication using tokens issued by third-party identity services. These types of tokens can be useful in allowing users to authenticate using their existing login credentials from other systems or services, without the need to create new usernames and passwords for the OPCUA server:

The OPCUA server currently supports the following types of issued tokens:

  • JWT

  • Azure

  • OAuth2

To configure authentication using issued tokens, it is necessary to add a section with tokenType equal to IssuedToken. Each section should include the following fields:

  • policyId name of the policy.

  • tokenType equals to IssuedToken

  • issuerEnpointUrl URL of the identiti server which issues tokens.

  • issuedTokenType type of the issued token: JWT, Azure etc…

  • securityPolicyUri encryption parameters should be applied to token.

    If absent then token is sent without encryption.

Here is an example of how to configure different issued token policies:

local issuedIdentityTokens = {
  {
    policyId = "jwt",
    tokenType = ua.Types.UserTokenType.IssuedToken,
    issuedTokenType = ua.Types.IssuedTokenType.JWT,
    securityPolicyUri = ua.Types.SecurityPolicy.Basic128Rsa15
  },
  {
    policyId = "jwt",
    tokenType = ua.Types.UserTokenType.IssuedToken,
    issuedTokenType = ua.Types.IssuedTokenType.JWT,
    securityPolicyUri = ua.Types.SecurityPolicy.Basic128Rsa15
  },
  {
    policyId = "azure",
    tokenType = ua.Types.UserTokenType.IssuedToken,
    issuedTokenType = ua.Types.IssuedTokenType.Azure,
    securityPolicyUri = ua.Types.SecurityPolicy.Basic128Rsa15
  },
  {
    policyId = "azure",
    tokenType = ua.Types.UserTokenType.IssuedToken,
    issuedTokenType = ua.Types.IssuedTokenType.Azure,
  },
  {
    policyId = "oauth2",
    tokenType = ua.Types.UserTokenType.IssuedToken,
    issuedTokenType = ua.Types.IssuedTokenType.OAuth2,
    securityPolicyUri = ua.Types.SecurityPolicy.Basic128Rsa15
  },
}

Full source

The authentication callback for all types of issued tokens is called with three parameters:

  • tokenType is equal to one of “jwt”, “oauth2”, “azure”.

  • token with token content.

  • issuerEnpointUrl The parameter issuerEndpointUrl specifies the URL

    of the server that issued the token. The value of this field is taken from the corresponding token policy in the userIdentityTokens field of the configuration table.

local function authenticate(tokenType, token, issuerEnpointUrl)

  if tokenType == "jwt" then
    return true
  elseif tokenType == "azure" then
    return true
  elseif tokenType == "oauth2" then
    return true
  end

  return false
end

Full source