Skip to main content

Middle Tier Security with XPO

  • 13 minutes to read

If you use client-side security mode in a WinForms application, a user can find the connection string in the application’s configuration file and access the database directly. This means that the user can bypass the Security System implemented in your application. To prevent this, you can implement the Middle Tier application server that is a WCF (.NET Framework) or an ASP.NET Core (.NET) service between the client application and the database server. This Middle Tier Server filters out the secured data. In this case, clients cannot access the database server directly. The diagram below illustrates this configuration.

General Information

The following image shows how a WinForms application with the Middle Tier Security interacts with the database:

Load Data From the Database

  1. The unsecure server-side Session loads data from the database according to the criteria based on Security permissions.
  2. The secured server-side Session copies objects from the unsecure server-side Session. If a field value does not meet the permission criterion, it is replaced with the default value in the copied objects. The copied objects are serialized and passed to the client-side Session.
  3. The client-side Session deserializes these objects. The deserialized objects are available to users.

Save Data to the Database

  1. The client-side Session serializes objects and passes them to the secure server-side Session.
  2. The secure server-side Session deserializes objects and copies their values that meet the Security permissions to the unsecure server-side Session.
  3. The unsecure server-side Session saves the passed values into original objects in the database.

Implement the Middle Tier Security (WCF) in WinForms Applications (.NET Framework)

Important

The Solution Wizard generates the code shown in this help topic when you create an application. Follow this article if you want to implement the demonstrated functionality in an existing XAF solution.

Create and Configure the Server

Create a Console Application Server Project

Follow the steps below to add a console application server project to your XAF solution:

  1. Right-click the solution in the Solution Explorer.
  2. In the invoked context menu, choose Add | New Project….
  3. Choose the DevExpress v24.1 XAF Template Gallery template.
  4. Specify the project name (for example, MySolution.AppServer) and click OK.
  5. Select the Application Server Project in the Solution Wizard and click Next.
  6. Specify what type of authentication you use (Standard or Active Directory) and click Finish.

The created application server connects to your application’s database or creates a new database if it does not exist, and updates the database.

Note

You can convert this console application to a Windows Service and deploy it in the production environment. For more information, refer to the following help topic: Run the Application Server as a Windows Service (.NET Framework).

Configure the Application Server

Follow the steps below to specify the client application name, Security System settings, modules used in the client application, and the database connection string in the application server.

In the Program.cs (Program.vb) File
  1. Set the ServerApplication.ApplicationName property to the client application’s name (see XafApplication.ApplicationName).
  2. Add a reference to the platform-dependent Modules to the Application Server project. To do this, right-click this project and choose Add reference…. In the invoked dialog, switch to the Projects tab, select Module projects, and click OK.
  3. Populate the ServerApplication.Modules collection with modules referenced in your client application. Refer to the InitializeComponent method of your WinApplication descendant to find these modules.
static class Program {
    [STAThread]
    static void Main(string[] args) {
        try {
            // ...
            ServerApplication serverApplication = new ServerApplication();
            serverApplication.ApplicationName = "MySolution";
            serverApplication.Modules.BeginInit();
            serverApplication.Modules.Add(new DevExpress.ExpressApp.Security.SecurityModule());
            serverApplication.Modules.Add(new MySolution.Module.Win.MySolutionWindowsFormsModule());
            serverApplication.Modules.EndInit();
            // ...
        }
    }
}
In the App.config File

Specify the connection string to access your database server in the App.config file.

<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="ConnectionString" connectionString=
        "Integrated Security=SSPI;Pooling=false;
        Data Source=(localdb)\mssqllocaldb;Initial Catalog=MySolution" />
  </connectionStrings>
  <!-- ... -->
</configuration>

Configure the Client Application

Follow the steps below to configure your Windows Forms application.

  1. Add a reference to System.ServiceModel.dll and DevExpress.ExpressApp.Security.v24.1.dll assemblies to the application project.
  2. Open the Program.cs (Program.vb) file and apply the following changes:

    using System.ServiceModel;
    using DevExpress.ExpressApp.Security.ClientServer.Wcf;
    // ...
    static class Program {
        [STAThread]
        static void Main() {
            // ...
            string connectionString = "net.tcp://localhost:1451/DataServer";
            winApplication.DatabaseUpdateMode = DatabaseUpdateMode.Never;
            WcfSecuredClient wcfSecuredClient = new WcfSecuredClient(WcfDataServerHelper.CreateNetTcpBinding(), 
                new EndpointAddress(connectionString));
            MiddleTierClientSecurity security = new MiddleTierClientSecurity(wcfSecuredClient);
            security.IsSupportChangePassword = true;
            winApplication.Security = security;
            winApplication.CreateCustomObjectSpaceProvider +=
                delegate(object sender, CreateCustomObjectSpaceProviderEventArgs e) {
                    e.ObjectSpaceProvider = new MiddleTierServerObjectSpaceProvider(wcfSecuredClient);
                };
            // comment or remove the following line
            // winApplication.GetSecurityStrategy().RegisterXPOAdapterProviders();
            // ...
            winApplication.Setup();
            winApplication.Start();
            wcfSecuredClient.Dispose();
            // ...
        }
    }
    

    Note that the IsSupportChangePassword property only enables the ChangePasswordByUser and ResetPasswords Actions and does not grant write permission to the user’s StoredPassword property. Specify the corresponding Member Permission to allow non-administrative users to change their passwords.

    In the example above, the server hostname is hardcoded. Alternatively, you can use the ConfigurationManager object to read the connection string from the configuration file.

  3. Unconditionally throw an exception when the DatabaseVersionMismatch event occurs while the compatibility check is performed on the server. To do this, modify the event handler in the WinApplication.cs (WinApplication.vb) file, as shown below:

    public partial class MySolutionWindowsFormsApplication : WinApplication {
        //...
        private void MySolutionWindowsFormsApplication_DatabaseVersionMismatch(
            object sender, DevExpress.ExpressApp.DatabaseVersionMismatchEventArgs e) {
            throw new InvalidOperationException(
                "The application cannot connect to the specified database " +
                "because the latter does not exist or its version is older " +
                "than that of the application.");
            }
        }
    }
    
  4. In the platform-agnostic Module, register the Security System types:

    • Reference the DevExpress.ExpressApp.Security.v24.1 and DevExpress.Persistent.BaseImpl.Xpo.v24.1 assemblies in the Module project.
    • Open the Module.cs (Module.vb) file and override the GetDeclaredExportedTypes method, as shown below:
    using DevExpress.Persistent.BaseImpl.PermissionPolicy;
    // ...
    public sealed partial class MySolutionModule : ModuleBase {
        // ...
        protected override IEnumerable<Type> GetDeclaredExportedTypes() {
            List<Type> result = new List<Type>(base.GetDeclaredExportedTypes());
            result.AddRange(new Type[] { typeof(PermissionPolicyUser), typeof(PermissionPolicyRole) });
            return result;
        }
    }
    

Important

The application server does not pass the role type to the client, and the Role navigation item is not available in the Navigation System. The following help topic describes how to add this item: Add an Item to the Navigation Control. (The List View identifier is “PermissionPolicyRole_ListView”.)

Run the Server and Client Applications

  1. Set the Application Server project as a startup project in the Solution Explorer and run the server. The console window displays the following output:

    Setup... 
    CheckCompatibility...
    Starting server...
    Server is started. Press Enter to stop.
    

    Note

    If a Windows Security Alert dialog is also displayed, click Allow access within this dialog.

  2. To run the client application, right-click the application project in the Solution Explorer and choose Debug | Start new instance.

The image below shows the server and client applications.

ClientServer_Wcf_StandardAuth

Display Server-Side Exception Details in the Client Application (.NET Framework)

You can pass server errors (for example, “The server was unable to process the request due to an internal error“) to the client application during the debug process. To do this, modify the WcfXafServiceHost object initialization, as shown below:

static class Program {
    [STAThread]
    static void Main(string[] args) {
        try {
            // ...
            WcfXafServiceHost serviceHost = new WcfXafServiceHost(connectionString, dataServerSecurityProvider);
            ServiceDebugBehavior debug = serviceHost.Description.Behaviors.Find<ServiceDebugBehavior>();
            if(debug == null) {
                serviceHost.Description.Behaviors.Add(
                new ServiceDebugBehavior() { IncludeExceptionDetailInFaults = true });
            }
            else {
                if(!debug.IncludeExceptionDetailInFaults) {
                    debug.IncludeExceptionDetailInFaults = true;
                }
            }
            // ...
        }
    }
}

Remove this code before you deploy your service in the production environment. For more information, refer to the following article: ServiceBehaviorAttribute.IncludeExceptionDetailInFaults Property.

Troubleshooting (.NET Framework)

If the error “The application cannot connect to the specified database“ occurs on the client, ensure that the client and server applications have the same module set. If the set is different for these applications, the Visual Studio Output window and client application log (eXpressAppFramework.log) shows the following message:

module 'SystemWindowsFormsModule' (DevExpress.ExpressApp.Win.v24.1). Local version: 24.1.7.0, Version on DB: 0.0.0.0

This message indicates that the server’s Modules collection does not include the SystemWindowsFormsModule module. (The missing module version is displayed as “0.0.0.0”.)

To fix the issue, add this module to the ServerApplication.Modules collection as shown in the Create and Configure the Server section of this topic.

Implement the Middle Tier Security in WinForms Applications (.NET)

In WinForms Applications for .NET, you can use the ASP.NET Core Middle Tier Server. See the following topics for details:

Important Notes

Common Notes

  • ASP.NET Web Forms and ASP.NET Core Blazor applications use the client-server model. You do not need to implement the additional Middle Tier server in these applications.
  • The Middle Tier service and database can be installed on the same server. The application server can also be installed on a user workstation with the application, but this configuration does not improve security.
  • The Security System displays the default property value instead of its actual value if access to a property is denied. These values may match. Use the SecuritySystem.IsGranted method to determine which value is displayed.
  • The OnSaving and OnDeleting methods of a business class can be called multiple times because Integrated Mode and Middle Tier Security use more than one Session/DbContext object. If you implement custom logic in these methods, check whether a new value is already assigned to a property. This helps you avoid incorrect results. The following article describes how to do this with XPO: XPO Best Practices.
  • Detail Views do not display changes made to an object within a transaction (for example, an auto-generated sequential number for an XPO business object) even if you saved this object in a View. These changes are made on the server only and are not automatically passed to the client application. To show these changes, reload the object. If you want to reload the object on each Save operation, override the business class’s OnSaved method. The following example demonstrates how to override this method to reload an object on the client:

    using DevExpress.Persistent.BaseImpl;
    // ...
    public class DemoObject : BaseObject {
        // ...
        protected override void OnSaved() {
            base.OnSaved();
            // 0 is the default value
            if (Number == 0) {
                Session.Reload(this);
            }
        }
    }
    
  • The Session.DataLayer property is null in the secured Session. Instead of DataLayer, we recommend that you use the View.ObjectSpace property to query and change data from the database. This technique is also recommended for non-secure applications.

    If this recommendation does not apply to your scenario, use the Session.ObjectLayer property instead of DataLayer.

    You can also execute different code on the server and client depending on the DataLayer and ObjectLayer property values. The following example demonstrates how to do it:

    using DevExpress.ExpressApp.Security.ClientServer;
    using DevExpress.Persistent.BaseImpl;
    // ...
    public class DemoObject : BaseObject {
        // ...
        protected override void OnSaving() { 
            if(Session.DataLayer != null && !(Session.ObjectLayer is SecuredSessionObjectLayer)) { 
                // Server-side code
            } 
            else {  
                // Client-side code 
            } 
            base.OnSaving(); 
        }
    }
    

.NET 6-Specific Note

If you use custom permission requests, custom logon parameters, or other types that should be serialized (for example, non-persistent objects), use the static WebApiDataServerHelper.AddKnownType method to register them before a data server is initialized. Register these types on the server and client. Do not use this method to register business classes.

.NET Framework-Specific Notes

See Also