Hide menu Last updated: Oct 27 2016
Area: Episerver CMS Applies to versions: 10.1 and higher
Other versions:

EPiServer CMS UI AspNetIdentity OWIN authentication

You can configure the application to use EPiServer AspNetIdentity as the authentication module for managing users and roles. This configuration requires the following NuGet package as a dependency: EPiServer.CMS.UI.AspNetIdentity.

To use and configure EPiServer AspNetIdentity OWIN-based authentication:

  1. Set the authentication mode in the system.web section of the web.config file as shown:
    <authentication mode="None"></authentication>
  2. Clear the membership and rolemanager providers from web.config as shown:
    <membership><providers><clear /></providers></membership>
    <roleManager enabled="false"><providers><clear /></providers></roleManager>
  3. Because OWIN pipeline is a startup class needed to configure the application, add the following code to the startup class:
    using EPiServer.Cms.UI.AspNetIdentity;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.Owin;
    using Microsoft.Owin;
    using Microsoft.Owin.Security.Cookies;
    using Owin;
    using System; 
    
    [assembly: OwinStartup(typeof(Startup))]
    
    public void Configuration(IAppBuilder app)
    {
       // Add CMS integration for ASP.NET Identity
       app.AddCmsAspNetIdentity<ApplicationUser>();   
       // Use cookie authentication
       app.UseCookieAuthentication(new CookieAuthenticationOptions
       {
         AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
         LoginPath = new PathString(login-path),
         Provider = new CookieAuthenticationProvider
         {
                 OnValidateIdentity = 
                    SecurityStampValidator.OnValidateIdentity<ApplicationUserManager<ApplicationUser>,ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => manager.GenerateUserIdentityAsync(user))
         }
       });
    }

The EPiServer.CMS.UI.ApsNetIdentity NuGet package implements the UIUsersManager, UIRoleManager, SecurityEntityProvider and SignInManager providers, which need to be integrated with the Episerver user interface. This means the users, roles and access rights can be managed from admin view. And, the Episerver user interface login page (/util/login.aspx) can be used for login.

Custom user database

By default, the ApplicationContext uses the EPiServerDB as a connection string name to save AspNet Users and roles. You can override it like this: 

app.AddCmsAspNetIdentity<ApplicationUser>(new ApplicationOptions() { ConnectionStringName = " connection string name" });

Custom user model

There are two ways to define a custom user model.

  • Inherit from EPiServer.Cms.UI.AspNetIdentity.ApplicationUser, like this:
    public class CustomUser : ApplicationUser
    {
       //your custom properites
    }
  • Inherit from Microsoft.AspNet.Identity.EntityFramework.IdentityUser and the EPiServer.Shell.Security.IUIUser interfaces, like this:
     public class CustomUser : IdentityUser, IUIUser
        {
            public string Comment { get; set; }
            public bool IsApproved { get; set; }
            public bool IsLockedOut { get; set; }
    
            [Column(TypeName = "datetime2")]
            public DateTime CreationDate { get; set; }
            
            [Column(TypeName = "datetime2")]
            public DateTime? LastLockoutDate { get; set; }
            
            [Column(TypeName = "datetime2")]
            public DateTime? LastLoginDate { get; set; }
    
            public string PasswordQuestion{get;}
    
            public string ProviderName
            {
                get { return "MyProviderName"; }
            }
    
            [NotMapped]
            public string Username 
            { 
                get { return base.UserName; } 
                set { base.UserName = value; }
            }
        }

After defining a custom user model, you need to configure it in the OWIN startup class, like this:

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // Add CMS integration for ASP.NET Identity             
            app.AddCmsAspNetIdentity<CustomUser>();

            // Use cookie authentication
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString(YourLoginPath or "/Util/Login.aspx"),
                Provider = new CookieAuthenticationProvider
                {
                 OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager<CustomUser>, CustomUser>(
                   validateInterval: TimeSpan.FromMinutes(30),
                   regenerateIdentity: (manager, user) => manager.GenerateUserIdentityAsync(user))
                }
            });
        }
    }

SecurityEntityProvider 

The EPiServer.CMS.UI.AspNetIdentity implements and registers the UIUserProvider, UIRoleProvider, UISignInManager and SecurityEntity provider in the container. To override them, you need to programmatically register it in the InitializeModule, like this:

    [EPiServer.Framework.InitializableModule]
    [EPiServer.Framework.ModuleDependency(typeof(EPiServer.Cms.UI.AspNetIdentity.ApplicationSecurityEntityInitialization))]
    [EPiServer.Framework.ModuleDependency(typeof(EPiServerUIInitialization))]
    public class MyInitializeModule : EPiServer.ServiceLocation.IConfigurableModule
    {
        public void ConfigureContainer(EPiServer.ServiceLocation.ServiceConfigurationContext context)
        {
           //Configure your providers
        }
        public void Initialize(EPiServer.Framework.Initialization.InitializationEngine context) { }
        public void Uninitialize(EPiServer.Framework.Initialization.InitializationEngine context) { }

Extend the Cms Asp.net Identity implementation

The Asp.net Identity system is using the Owin pipeline which makes it a bit harder to replace our default implementation, it is not just a matter of registering a new class in the IoC container.

Instead, you need to take control of the pipeline setup. We are exposing the default create delegates for all standard Asp.net Identity classes, which means that you need to create your own Pipeline initialization method and call the create delegates in the correct order. This is an example on how you can do that in your own IAppBuilder extension method.

public static class ApplicationBuilderExtensions
{
    public static IAppBuilder SetupCustomAspNetIdentity<TUser>(this IAppBuilder app) where TUser : IdentityUser, IUIUser, new()
    {
        var applicationOptions = new ApplicationOptions
        {
            DataProtectionProvider = app.GetDataProtectionProvider()
        };

        // Configure the db context, user manager and signin manager to use a single instance per request by using
        // the default create delegates
        app.CreatePerOwinContext<ApplicationOptions>(() => applicationOptions);
        app.CreatePerOwinContext<ApplicationDbContext<TUser>>(ApplicationDbContext<TUser>.Create);
        app.CreatePerOwinContext<ApplicationRoleManager<TUser>>(ApplicationRoleManager<TUser>.Create);
        app.CreatePerOwinContext<ApplicationUserManager<TUser>>(ApplicationUserManager<TUser>.Create);
        app.CreatePerOwinContext<ApplicationSignInManager<TUser>>(ApplicationSignInManager<TUser>.Create);

        // Configure the application 
        app.CreatePerOwinContext<UIUserProvider>(ApplicationUserProvider<TUser>.Create);
        app.CreatePerOwinContext<UIRoleProvider>(ApplicationRoleProvider<TUser>.Create);
        app.CreatePerOwinContext<UIUserManager>(ApplicationUIUserManager<TUser>.Create);
        app.CreatePerOwinContext<UISignInManager>(ApplicationUISignInManager<TUser>.Create);

        // Saving the connection string in the case dbcontext be requested from none web context
        ConnectionStringNameResolver.ConnectionStringNameFromOptions = applicationOptions.ConnectionStringName;
        
        return app; 
    }
}

When you have your new setup method you need to call that method in the Startup instead of the built-in one.

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ...

        // Add CMS integration for ASP.NET Identity
        app.SetupCustomAspNetIdentity<ApplicationUser>();

        ...
    }
}

 When you have your custom pipeline running, it is possible to change things like the default PasswordHasher, the default PasswordValidator, etc.

The first thing you need to do is to create a new create delegate for the ApplicationUserManager and change the relevant values.

public static class ApplicationBuilderExtensions
{
    public static ApplicationUserManager<TUser> CreateApplicationUserManager<TUser>(IdentityFactoryOptions<ApplicationUserManager<TUser>> options, IOwinContext context) where TUser : IdentityUser, IUIUser, new()
    {
        var manager = new ApplicationUserManager<TUser>(new UserStore<TUser>(context.Get<ApplicationDbContext<TUser>>()));

        // Configure validation logic for usernames
        manager.UserValidator = new UserValidator<TUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

        // Change Password hasher
        manager.PasswordHasher = new SqlPasswordHasher();

        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true
        };

        // Configure user lockout defaults
        manager.UserLockoutEnabledByDefault = true;
        manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
        manager.MaxFailedAccessAttemptsBeforeLockout = 5;

        var provider = context.Get<ApplicationOptions>().DataProtectionProvider.Create("EPiServerAspNetIdentity");
        manager.UserTokenProvider = new DataProtectorTokenProvider<TUser>(provider);

        return manager;
    }
}

When you have your own create delegate you have to replace the default create delegate in the SetupCustomAspNetIdentity method.

Change

app.CreatePerOwinContext<ApplicationUserManager<TUser>>(ApplicationUserManager<TUser>.Create);


to

app.CreatePerOwinContext<ApplicationUserManager<TUser>>(CreateApplicationUserManager);

 

Comments

I'm having trouble using a custom user object, because if i take a database that has never had asp.net identity in it, the first time i start the site, it will be generated with the the built-in Microsoft.AspNet.Identity.EntityFramework.IdentityUser
I am suspecting it is because of the initializer EPiServer.Cms.UI.AspNetIdentity.ApplicationSecurityEntityInitialization. That class is calling 'new ApplicationDbContext<IdentityUser>(ConnectionStringNameResolver.Resolve())', which I am guessing will cause entityframework to create all tables with the built-in user type instead of my custom one.

(tried with EPiServer.CMS.UI.AspNetIdentity 10.4.0)

I found the problem. Its the example code, and I feel a bit silly I didnt spot it at first.

The article suggest two ways of using your own custom user class:

1. Inherit IdentityUser and implement IUIUser
2. Inherit ApplicationUser

Now, everything works when I use approach 2. But due to how the example is written, approach 1 fails:

#ApplicationUser.cs
    public virtual string PasswordQuestion
    {
      get {  return (string) null; }
    }
#IUIUser implementation example
public string PasswordQuestion{get;set;}

Because of the setter in the example, EF tries to persist the value to the DB, in where it doesnt exist and will throw an Invalid column name 'PasswordQuestion'. exception if I try to create a user.

Thanks Erik! The code example has been updated now.