Back to Devexpress

Customize Standard Authentication Behavior and Supply Additional Logon Parameters (.NET Applications)

expressappframework-404264-data-security-and-safety-security-system-authentication-customize-standard-authentication-behavior-and-supply-additional-logon-parameters-customize-authentication-behavior-net.md

latest14.8 KB
Original Source

Customize Standard Authentication Behavior and Supply Additional Logon Parameters (.NET Applications)

  • Nov 04, 2025
  • 10 minutes to read

When an XAF application uses the AuthenticationStandard authentication, the default login form displays User Name and Password editors. This topic explains how to customize this form and show Company and Application User lookup editors instead of User Name.

View Example: XAF - Customize Logon Parameters

Define a Data Model for Custom Parameters

  1. Add a Company class to your project. This class should contain company names and a list of ApplicationUser objects as a part of a one-to-many relationship.

  2. Add the second part of this relationship to the ApplicationUser class generated by the Template Kit.

  3. Add the Company class to your application’s DbContext.

Add Custom Parameters to the Login Window

The default login form uses an AuthenticationStandardLogonParameters Detail View that includes UserName and Password fields. To add custom fields, create a CustomLogonParameters class in your application’s module project (MySolution.Module).

csharp
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.ConditionalAppearance;
using DevExpress.ExpressApp.DC;
using DevExpress.ExpressApp.Security;
using DevExpress.Persistent.Base;
using Microsoft.Extensions.DependencyInjection;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

namespace EFCoreCustomLogonAll.Module.Authentication;

[DataContract]
[DomainComponent]
[DisplayName("Log In")]
[Appearance("CompanyIsNull", TargetItems = $"{nameof(Password)}, {nameof(ApplicationUser)}", Criteria = "IsNull([Company])", Enabled = false)]
public class CustomLogonParameters : IAuthenticationStandardLogonParameters, ISupportClearPassword, INotifyPropertyChanged, IObjectSpaceLink {
    private CompanyDTO? company;
    private ApplicationUserDTO? applicationUser;
    private string? password;
    private IReadOnlyList<CompanyDTO>? _companies = null;
    private IObjectSpace? objectSpace;
    private ILogonDataProvider? logonDataProvider;

    [Browsable(false)]
    public string? UserName { get; set; }

    [JsonIgnore]
    [ImmediatePostData]
    [DataSourceProperty("Companies", DataSourcePropertyIsNullMode.SelectAll)]
    [IgnoreDataMember]
    public CompanyDTO? Company {
        get { return company; }
        set {
            if(value == company) return;
            company = value;
            if(company == null || ApplicationUser?.CompanyID != company.ID) {
                ApplicationUser = null;
            }
            OnPropertyChanged(nameof(CompanyDTO));
        }
    }
    [Browsable(false)] // hide from UI
    [JsonIgnore]
    [IgnoreDataMember]
    public IReadOnlyList<CompanyDTO>? Companies {
        get {
            if(_companies == null) {
                if(logonDataProvider != null) {
                    _companies = logonDataProvider.GetCompanies().AsReadOnly();
                } else {
                    _companies = Array.Empty<CompanyDTO>();
                }
            }
            return _companies;
        }
    }

    [JsonIgnore]
    [DataSourceProperty($"{nameof(Company)}.{nameof(Company.ApplicationUsers)}"), ImmediatePostData]
    [System.Runtime.Serialization.IgnoreDataMember]
    public ApplicationUserDTO? ApplicationUser {
        get { return applicationUser; }
        set {
            if(value == applicationUser)
                return;
            applicationUser = value;
            UserName = applicationUser?.UserName;
            OnPropertyChanged(nameof(ApplicationUser));
        }
    }

    [PasswordPropertyText(true)]
    [DataMember]
    public string? Password {
        get { return password; }
        set {
            if(password == value)
                return;
            password = value;
            OnPropertyChanged(nameof(Password));
        }
    }

    IObjectSpace? IObjectSpaceLink.ObjectSpace {
        get => objectSpace;
        set {
            objectSpace = value;
            if(objectSpace != null) {
                logonDataProvider = objectSpace.ServiceProvider.GetService<ILogonDataProvider>();
            }
            ApplicationUser = null;
            Company = null;
            _companies = null;
        }
    }

    private void OnPropertyChanged(string propertyName) {
        if(PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    void ISupportClearPassword.ClearPassword() {
        password = null;
    }

    public event PropertyChangedEventHandler? PropertyChanged;
}

Note the following implementation details:

  • The CustomLogonParameters class must implement the ISupportClearPassword and IAuthenticationStandardLogonParameters interfaces.

  • (Blazor specific) Properties with data types that cannot be serialized to JSON must be marked with JsonIgnoreAttribute.

  • (Blazor specific) To keep the cookie compact, assign null to unnecessary properties after login in the ISupportClearPassword.ClearPassword method.

  • (WinForms specific) Properties should be marked with the correct attributes based on whether they need to be serialized and transferred between the client and server after login:

  • (WinForms specific) If nullable reference types are enabled for the project, all properties in the CustomLogonParameters class should be marked as nullable.

Implement Data Transfer Objects (DTO)

  1. To access lists of companies and application users in login form (before authentication), implement CompanyDTO and ApplicationUserDTO data transfer objects.

  2. Implement the AuthenticationDataController with GetCompanies and GetApplicationUsers methods to supply data for the logon form. This data is consumed by the CustomLogonParameters class, which operates in Middle Tier and standard Blazor applications.

  3. Create the ILogonDataProvider service that contains abstract data retrieval logic.

  4. Implement this interface in platform-specific classes: MiddleTierClientLogonDataProvider or BlazorLogonDataProvider.

  5. Register platform-specific providers as services:

Customize Blazor Lookup Editors

A Blazor lookup editor displays the Edit button. Create a controller and call the View.CustomizeViewItemControl method to hide the button from the Company and Application User lookup editors.

csharp
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Blazor.Editors;
using EFCoreCustomLogonAll.Module.Authentication;

namespace EFCoreCustomLogonAll.Blazor.Server.Authentication {
    // https://supportcenter.devexpress.com/ticket/details/t1164456/blazor-lookup-list-view-s-allowedit-property-does-not-affect-visibility-of
    public class CustomLogonParameterLookupActionVisibilityController : ObjectViewController<DetailView, CustomLogonParameters> {
        protected override void OnActivated() {
            base.OnActivated();
            View.CustomizeViewItemControl<LookupPropertyEditor>(this, e => {
                e.HideEditButton();
            });
        }
    }
}

Pass Custom Classes to the Security System

In the application’s Startup.cs files, set the provider’s LogonParametersType option to CustomLogonParameters in the AddPasswordAuthentication method call.

Files: MySolution.Blazor.Server\Startup.cs, MySolution.MiddleTier\Startup.cs

csharp
builder.Security
// ...
    .UseIntegratedMode(options => {
    // ...
    })
    .AddPasswordAuthentication(options => {
        options.IsSupportChangePassword = true;
        options.LogonParametersType = typeof(CustomLogonParameters);
    });

Add Demo Data

Override the ModuleUpdater.UpdateDatabaseAfterUpdateSchema method to create companies, application users, and security roles.

csharp
using DevExpress.ExpressApp;
using DevExpress.Data.Filtering;
using DevExpress.Persistent.Base;
using DevExpress.ExpressApp.Updating;
using DevExpress.ExpressApp.Security;
using DevExpress.ExpressApp.SystemModule;
using DevExpress.ExpressApp.EF;
using DevExpress.Persistent.BaseImpl.EF;
using DevExpress.Persistent.BaseImpl.EF.PermissionPolicy;
using EFCoreCustomLogonAll.Module.BusinessObjects;
using Microsoft.Extensions.DependencyInjection;

namespace EFCoreCustomLogonAll.Module.DatabaseUpdate;

// For more typical usage scenarios, be sure to check out https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.Updating.ModuleUpdater
public class Updater : ModuleUpdater {
    public Updater(IObjectSpace objectSpace, Version currentDBVersion) :
        base(objectSpace, currentDBVersion) {
    }
    public override void UpdateDatabaseAfterUpdateSchema() {
        base.UpdateDatabaseAfterUpdateSchema();
        // ...
        var defaultRole = CreateDefaultRole();
        var adminRole = CreateAdminRole();
        ObjectSpace.CommitChanges(); //This line persists created object(s).  

        UserManager userManager = ObjectSpace.ServiceProvider.GetRequiredService<UserManager>();
        ApplicationUser userAdmin = userManager.FindUserByName<ApplicationUser>(ObjectSpace, "Admin");
        // If a user named 'Admin' doesn't exist in the database, create this user.
        if(userAdmin == null) {
            // Set a password if the standard authentication type is used.
            string EmptyPassword = "";
            userAdmin = userManager.CreateUser<ApplicationUser>(ObjectSpace, "Admin", EmptyPassword, (user) => {
                // Add the Administrators role to the user.
                user.Roles.Add(adminRole);
            }).User;
        }

        if(ObjectSpace.FindObject<Company>(null) == null) {
            Company company1 = ObjectSpace.CreateObject<Company>();
            company1.Name = "Company 1";
            company1.ApplicationUsers.Add(userAdmin);
            ApplicationUser user1 = userManager.CreateUser<ApplicationUser>(ObjectSpace, "Sam", "", (user) => {
                user.Roles.Add(defaultRole);
            }).User;
            ApplicationUser user2 = userManager.CreateUser<ApplicationUser>(ObjectSpace, "John", "", (user) => {
                user.Roles.Add(defaultRole);
            }).User;
            Company company2 = ObjectSpace.CreateObject<Company>();
            company2.Name = "Company 2";
            company2.ApplicationUsers.Add(user1);
            company2.ApplicationUsers.Add(user2);
        }
        ObjectSpace.CommitChanges(); //This line persists created object(s).
        // ...
    }
    // ...
}

Middle-Tier-Specific Steps

Complete the following steps to configure an application with Middle-Tier security to work with custom login parameters.

Register the CustomLogonParameters Class in Client Applications

The CustomLogonParameters type must be explicitly registered in the client application as a known login parameter type. Call the AddKnownType(Type) method in the following files: MySolution.Blazor.Server/Program.cs (for Blazor) and MySolution.Win/Program.cs (for WinForms).

csharp
public static int Main(string[] args) {
// ...
    WebApiDataServerHelper.AddKnownType(typeof(CustomLogonParameters));
    // ...
}

Implement AuthenticationController and JwtTokenProviderService Classes

Implement the following classes in Middle Tier server application:

  • The AuthenticationController class specifies the type for deserializing incoming JSON.

  • The JwtTokenProviderService class allows you to customize logic that makes authentication decisions against the custom set of login parameters specified by a user.

Add the following code to the OnCustomAuthenticate event handler in Startup.cs files of Blazor and WinForms Middle Tier clients:

csharp
builder.Security
// ...
    .UseMiddleTierMode(options => {
    // ...
        options.Events.OnCustomAuthenticate = (sender, security, args) => {
            args.Handled = true;
            HttpResponseMessage msg = args.HttpClient.PostAsJsonAsync("api/Authentication/Authenticate", (CustomLogonParameters)args.LogonParameters).GetAwaiter().GetResult();
            string token = (string)msg.Content.ReadFromJsonAsync(typeof(string)).GetAwaiter().GetResult();
            if(msg.StatusCode == HttpStatusCode.Unauthorized) {
                XafExceptions.Authentication.ThrowAuthenticationFailedFromResponse(token);
            }
            msg.EnsureSuccessStatusCode();
            args.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
        };

Run the Application

Run the application to see custom parameters (Company and Application User lookup editors) in the login window.

Note

  • When first running a Blazor application, it does not create a database or populate lookup editors. To create a database, either log in with an empty username or start the Middle Tier server.
  • If using Middle Tier security, start the Middle Tier server before launching the Blazor or Win Forms client.

See Also

XAF Blazor UI: How to extend the logon form - register a new user, restore a password