Implement a Security System User (ApplicationUser) Based on XAF Business Classes
- 4 minutes to read
XAF has a built-in PermissionPolicyUser
class for XPO and EF Core-based applications. This class implements basic functionality required to store Security System user data. The major limitation of the PermissionPolicyUser
class is that it can be used directly only in applications with a single authentication method: either password-based or Windows Active Directory-based.
When a user has multiple ways to log in, you need to store information for all authentication types and associate this information with the user. To do this, use the ISecurityUserWithLoginInfo (a descendant of ISecurityUser and IOAuthSecurityUser
) and ISecurityUserLoginInfo interfaces.
Additionally, if you want to support the user lockout feature (the capability to lock out users who fail to enter the correct password several times in a row), implement the ISecurityUserLockout interface in your security system user class.
Note
The XAF Solution Wizard generates classes that implement these interfaces automatically for projects created with v21.1 and later. You can find these implementations in the following files within your solution:
- SolutionName.Module\BusinessObjects\ApplicationUser.cs
- SolutionName.Module\BusinessObjects\ApplicationUserLoginInfo.cs
Follow the steps below to implement the required classes from scratch.
Add a class to store user login information. In this class, implement the ISecurityUserLoginInfo interface. You can use the following code as a reference implementation (XAF Solution Wizard generates equivalent code).
using DevExpress.ExpressApp.ConditionalAppearance; using DevExpress.ExpressApp.Security; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; // ... [Table("PermissionPolicyUserLoginInfo")] public class ApplicationUserLoginInfo : ISecurityUserLoginInfo { public ApplicationUserLoginInfo() { } [Browsable(false)] public virtual Guid ID { get; protected set; } [Appearance("PasswordProvider", Enabled = false, Criteria = "!(IsNewObject(this)) and LoginProviderName == '" + SecurityDefaults.PasswordAuthentication + "'", Context = "DetailView")] public virtual string LoginProviderName { get; set; } [Appearance("PasswordProviderUserKey", Enabled = false, Criteria = "!(IsNewObject(this)) and LoginProviderName == '" + SecurityDefaults.PasswordAuthentication + "'", Context = "DetailView")] public virtual string ProviderUserKey { get; set; } [Browsable(false)] public virtual Guid UserForeignKey { get; set; } [Required] [ForeignKey(nameof(UserForeignKey))] public virtual ApplicationUser User { get; set; } object ISecurityUserLoginInfo.User => User; }
Add an application user class that extends
PermissionPolicyUser
and implements theISecurityUserWithLoginInfo
interface required to associate multiple authentication methods with a user and theISecurityUserLockout
interface to support the user lockout feature. You can use the following code as a reference implementation (XAF Solution Wizard generates equivalent code).using DevExpress.ExpressApp; using DevExpress.ExpressApp.Security; using DevExpress.Persistent.BaseImpl.EF.PermissionPolicy; using System.Collections.ObjectModel; using System.ComponentModel; // ... [DefaultProperty(nameof(UserName))] public class ApplicationUser : PermissionPolicyUser, ISecurityUserWithLoginInfo, ISecurityUserLockout { [Browsable(false)] public virtual int AccessFailedCount { get; set; } [Browsable(false)] public virtual DateTime LockoutEnd { get; set; } [Browsable(false)] [DevExpress.ExpressApp.DC.Aggregated] public virtual IList<ApplicationUserLoginInfo> UserLogins { get; set; } = new ObservableCollection<ApplicationUserLoginInfo>(); IEnumerable<ISecurityUserLoginInfo> IOAuthSecurityUser.UserLogins => UserLogins.OfType<ISecurityUserLoginInfo>(); ISecurityUserLoginInfo ISecurityUserWithLoginInfo.CreateUserLoginInfo(string loginProviderName, string providerUserKey) { ApplicationUserLoginInfo result = ((IObjectSpaceLink)this).ObjectSpace.CreateObject<ApplicationUserLoginInfo>(); result.LoginProviderName = loginProviderName; result.ProviderUserKey = providerUserKey; result.User = this; return result; } }
Modify the Application Builder code as shown below so that XAF uses your custom classes to store security data:
File: MySolution.Blazor.Server\Startup.cs, MySolution.Win\Startup.cs, MySolution.WebApi\Startup.cs
// ... builder.Security .UseIntegratedMode(options => { // ... options.UserType = typeof(MySolution.Module.BusinessObjects.ApplicationUser); options.UserLoginInfoType = typeof(MySolution.Module.BusinessObjects.ApplicationUserLoginInfo); // ...