Middle Tier Security (XPO Only)

  • 13 minutes to read

In WinForms application with client-side security mode, a user can find the connection string in the application's configuration file and directly access the database tables. In this case, the user bypasses the Security System implemented in your application. The following article describes how to protect the connection string in your application: FAQ: Role-based Access Control & User Authentication API for DevExpress XPO and Microsoft Entity Framework Core ORMs (the How to protect database connection strings in desktop or mobile clients? section). To prevent this, you can implement the Middle Tier application server that is a WCF 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 secured server-side Session.
  2. The secured 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 passes values into original objects in the database.

Important Notes

  • The Middle Tier does not support the EF 6 and EF Core data model.
  • ASP.NET and Blazor applications use the client-server model and you do not need to implement the additional Middle Tier server in these applications.

  • The Middle Tier service and database can be on the same server. The application server can also be installed on the user workstation with the application, but this configuration does not improve security.
  • The Middle Tier application server is a WCF service. You can use approaches described in the following topic to secure and deploy it: Programming WCF 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 this Security mode uses 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 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 update a reload an object on the client:

    using DevExpress.Persistent.BaseImpl;
    // ...
    public class DemoObject : BaseObject {
        // ...
        protected override void OnSaved() {
            base.OnSaved();
            // The 0 is 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 approach 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(); 
        }
    }
    

Implement the Middle Tier Security

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.

A complete sample project is available in the DevExpress Code Examples database at http://www.devexpress.com/example=E4036.

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 v20.2 XAF Solution Wizard 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.

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.Add(new MySolution.Module.Win.MySolutionAspNetModule());
            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=.\SQLEXPRESS;Initial Catalog=MySolution" />
  </connectionStrings>
  <!-- ... -->
</configuration>

Important Notes

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.v20.2.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.v20.2 and DevExpress.Persistent.BaseImpl.v20.2 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

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

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.v20.2). Local version: 20.2.5.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.

See Also