2FA?

Vote:
 

Has anyone set up 2FA with EpiServer? For example Google Authenticator or something similar?

How simple/complicated would this be and are there any guides on setting this up?

We are using the standard authentication in EpiServer.

I found this here: https://world.optimizely.com/blogs/Joshua-Folkerts/Dates/2019/2/2-factor-authentication-in-episerver/ is it still valid? Seems there isn't much on this topic available. 

#264876
Edited, Oct 11, 2021 8:30
Vote:
 

As it's only a few years old that code would have been built around a fairly new version of the platform so is valid for pre CMS 12 solutions. So I would give it a go

#264877
Oct 11, 2021 8:42
Vote:
 

Thanks Scott,

So I am trying to work my way through the guide, I am unsure about:

public static IAppBuilder AddCmsTwoFactorAspNetIdentity<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);            app.CreatePerOwinContext<ApplicationDbContext<TUser>>(ApplicationDbContext<TUser>.Create);            app.CreatePerOwinContext<ApplicationRoleManager<TUser>>(ApplicationRoleManager<TUser>.Create);       app.CreatePerOwinContext<ApplicationUserManager<TUser>>(CreateApplicationUserManager);  

// 2 Factor Auto Registration            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;
        }

        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>>()))
            {
                UserLockoutEnabledByDefault = true,
                DefaultAccountLockoutTimeSpan = TimeSpan.FromMilliseconds(5),
                MaxFailedAccessAttemptsBeforeLockout = 5,

                PasswordValidator = new PasswordValidator
                {
                    RequiredLength = 6,
                    RequireNonLetterOrDigit = false,
                    RequireDigit = false,
                    RequireLowercase = false,
                    RequireUppercase = false
                }
            };

            manager.UserValidator = new UserValidator<TUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            manager.RegisterTwoFactorProvider("TwoFactor Authenticator", (IUserTokenProvider<TUser, string>)new TwoFactorAuthenticatorTokenProvider());
            var provider = context.Get<ApplicationOptions>().DataProtectionProvider.Create("EPiServerAspNetIdentity");
            manager.UserTokenProvider = new DataProtectorTokenProvider<TUser>(provider);

            return manager;
        }

Is this supposed to go into Business\AdministratorRegistrationPage.cs? Anyone know?

#264985
Oct 13, 2021 9:35
Scott Reed - Oct 13, 2021 9:37
This should just be added to any static class placed in the business folder. As you can see in the startup.cs file it's referenced as part of the startup in the first few lines of the Configuration method.
Vote:
 

Maybe these 2 issues are glaringly obvious to others and I'm missing them.

1. So in Startup.cs I have added the three lines as per the blog post:

// Register our two factor auth into the site.  
app.AddCmsTwoFactorAspNetIdentity<SiteApplicationUser>();
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

But in Visual Studio <SiteApplicationUser> is being red-underlined with the following message:

The type or namespace name 'SiteApplicationUser' could not be found (are you missing a using directive or an assembly reference?) 

Now as far as I understood from the blog post, we are setting up a new user store here. But there is no mention of any other steps required for this user store in the blog post. Any idea what the issue here is?

2. Again, as per the blog post, I have added the following:

public static IAppBuilder AddCmsTwoFactorAspNetIdentity<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);            app.CreatePerOwinContext<ApplicationDbContext<TUser>>(ApplicationDbContext<TUser>.Create);            app.CreatePerOwinContext<ApplicationRoleManager<TUser>>(ApplicationRoleManager<TUser>.Create);       app.CreatePerOwinContext<ApplicationUserManager<TUser>>(CreateApplicationUserManager);  

// 2 Factor Auto Registration            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;
        }

        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>>()))
            {
                UserLockoutEnabledByDefault = true,
                DefaultAccountLockoutTimeSpan = TimeSpan.FromMilliseconds(5),
                MaxFailedAccessAttemptsBeforeLockout = 5,

                PasswordValidator = new PasswordValidator
                {
                    RequiredLength = 6,
                    RequireNonLetterOrDigit = false,
                    RequireDigit = false,
                    RequireLowercase = false,
                    RequireUppercase = false
                }
            };

            manager.UserValidator = new UserValidator<TUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            manager.RegisterTwoFactorProvider("TwoFactor Authenticator", (IUserTokenProvider<TUser, string>)new TwoFactorAuthenticatorTokenProvider());
            var provider = context.Get<ApplicationOptions>().DataProtectionProvider.Create("EPiServerAspNetIdentity");
            manager.UserTokenProvider = new DataProtectorTokenProvider<TUser>(provider);

            return manager;
        }

I have added this into an existing file called AdministratorRegistrationPage.cs (perhaps this is the wrong file and I am supposed to add the above snippet into a different file or new file altogether?).

And here in this line:

manager.RegisterTwoFactorProvider("TwoFactor Authenticator", (IUserTokenProvider<TUser, string>)new TwoFactorAuthenticatorTokenProvider());

The TwoFactorAuthenticatorTokenProvider is being red-underlined with the following message:

The type or namespace name 'TwoFactorAuthenticatorTokenProvider' could not be found (are you missing a using directive or an assembly reference?)

Again, no mention of any related steps in the blog post, any idea what the issue here is?

#265062
Oct 14, 2021 8:23
Scott Reed - Oct 14, 2021 9:05
For SiteApplicationUser this should just be whatever User model you're using. The default is ApplicationUser but often people have inheirted models with extra properties.

AdministratorRegistrationPage is the wrong file to add it, you should create a static file and add to it.

Regarding TwoFactorAuthenticatorTokenProvider is assume it's coming from one of the packages that you should have installed via

Install-Package Microsoft.AspNet.Identity.Owin
Install-Package Microsoft.Owin.Security.OAuth
Install-Package Microsoft.Owin.Security.Cookies
Install-Package OtpSharp.Core
Install-Package Wiry.Base32.Patch
EpiNewbie - Oct 14, 2021 9:23
Thank you as always Scott!

So we are in fact using ApplicationUser but in the example in the blog post they commented it out. And if I am reading the explanation right, ApplicationUser is in fact meant to be replaced?
Scott Reed - Oct 14, 2021 9:33
I think you should be able to use ApplicationUser, I can't see anything that details anything specifically in a custom model and there is also a line about replacing it with whatever your model is. So I'd try with ApplicationUser first, there's no reason to use a different one unless SiteApplicationUser has some extra required properties.

It's a shame this blog doesn't have a github repo, it's a little confusing in how it's written
EpiNewbie - Oct 14, 2021 11:10
Scott, so looking through the blog post, and seeing feedback from others in the comments section, it looks like this is not implementable as I do not see anything on what's necessary to build all the logic in the views? Would you agree? My impression is that the blog post is explaining about 50% of what's needed?
Scott Reed - Oct 14, 2021 12:30
I think there's some things to figure out and it seems others have struggled too. I've only had time to scan over it but I think you're right, ideally there would have been a github repo to get more out of but it's quite complex so you may struggle
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.