Umbraco CMS
CloudHeartcoreDXPMarketplace
15.latest
15.latest
  • Umbraco CMS Documentation
  • Legacy Documentation
    • Our Umbraco
    • GitHub
  • Release Notes
  • Contribute
  • Sustainability Best Practices
  • Fundamentals
    • Get to know Umbraco
    • Setup
      • Requirements
      • Installation
        • Install using .NET CLI
        • Running Umbraco in Docker using Docker Compose
        • Install using Visual Studio
        • Local IIS With Umbraco
        • Install using Visual Studio Code
        • Installing Nightly Builds
        • Running Umbraco on Linux/macOS
        • Unattended Installs
      • Upgrade your project
        • Version Specific Upgrades
          • Upgrade from Umbraco 8 to the latest version
          • Migrate content to Umbraco 15
          • Migrate content to Umbraco 8
          • Minor upgrades for Umbraco 8
          • Upgrade to Umbraco 7
          • Minor upgrades for Umbraco 7
      • Server setup
        • Running Umbraco On Azure Web Apps
        • Hosting Umbraco in IIS
        • File And Folder Permissions
        • Runtime Modes
        • Running Umbraco in Docker
        • Umbraco in Load Balanced Environments
          • Load Balancing Azure Web Apps
          • Standalone File System
          • Advanced Techniques With Flexible Load Balancing
          • Logging With Load Balancing
    • Backoffice
      • Sections
      • Property Editors
        • Built-in Property Editors
          • Checkbox List
          • Collection
          • Color Picker
          • Content Picker
          • Document Picker
          • DateTime
          • Date
          • Decimal
          • Email Address
          • Eye Dropper Color Picker
          • File Upload
          • Image Cropper
          • Label
          • Markdown Editor
          • Media Picker
          • Member Group Picker
          • Member Picker
          • Multi Url Picker
          • Repeatable Textstrings
          • Numeric
          • Radiobutton List
          • Slider
          • Tags
          • Textarea
          • Textbox
          • Toggle
          • User Picker
          • Block Editors
            • Block Grid
            • Block List
            • Block Level Variance
          • Dropdown
          • Rich Text Editor
            • Configuration
            • Extensions
            • Blocks
            • Style Menu
            • Change Rich Text Editor UI
          • Rich Text Editor TinyMce
            • Configuration
            • Styles
            • Plugins
            • Blocks
      • Login
      • Document Blueprints
      • Sidebar
      • Log Viewer
      • Language Variants
      • Settings Dashboards
    • Data
      • Defining Content
        • Default Document Types
        • Document Type Localization
      • Creating Media
        • Default Data/Media Types
      • Members
      • Data Types
        • Default Data Types
      • Scheduled Publishing
      • Using Tabs
      • Users
        • API Users
      • Relations
      • Dictionary Items
      • Content Version Cleanup
    • Design
      • Templates
        • Basic Razor Syntax
        • Razor Cheatsheet
      • Rendering Content
      • Rendering Media
      • Partial Views
      • Stylesheets And JavaScript
    • Code
      • Service APIs
      • Subscribing To Notifications
      • Creating Forms
      • Debugging
        • Logging
      • Source Control
  • Implementation
    • Learn how Umbraco works
    • Routing
      • Controller & Action Selection
      • Execute Request
      • Request Pipeline
    • Custom Routing
      • Adding a hub with SignalR and Umbraco
    • Controllers
    • Data Persistence (CRUD)
    • Composing
    • Integration Testing
    • Nullable Reference Types
    • Services and Helpers
      • Circular Dependencies
    • Unit Testing
  • Customizing
    • Extend and customize the editing experience
    • Project Bellissima
    • Setup Your Development Environment
      • Vite Package Setup
      • TypeScript setup
    • Extension Overview
      • Extension Registry
        • Extension Registration
        • Extension Manifest
        • Replace, Exclude or Unregister
      • Extension Types
        • Sections
          • Sections
          • Section Sidebar
          • Section View
        • Workspaces
          • Workspace Actions
          • Workspace Context
          • Workspace Views
        • Route Registration
        • Menu
        • Header Apps
        • Icons
        • Block Custom View
        • Bundle
        • Kind
        • App Entry Point
        • Backoffice Entry Point
        • Extension Conditions
        • Dashboards
        • Entity Actions
        • Entity Bulk Actions
        • Entity Create Option Action
        • Property Value Preset
        • Trees
        • Global Context
        • Localization
        • Modals
          • Confirm Dialog
          • Custom Modals
      • Extension Kind
      • Extension Conditions
      • Custom Extension types
    • Foundation
      • Working with Data
        • Repositories
        • Context API
        • Store
        • States
      • Contexts
        • Property Dataset Context
      • Umbraco Element
        • Controllers
          • Write your own controller
      • Sorting
      • Routes
      • Icons
      • Backoffice Localization
      • Terminology
    • Sections & Trees
    • Searchable Trees (ISearchableTree)
    • Property Editors
      • Property Editors Composition
        • Property Editor Schema
        • Property Editor UI
      • Property Value Converters
      • Property Actions
      • Integrate Property Editors
      • Tracking References
      • Content Picker Value Converter Example
      • Property Dataset
      • Integrate Validation
    • Workspaces
    • Umbraco Package
    • UI Library
  • Extending
    • Build on Umbraco functionality
    • Health Check
      • Health Check Guides
        • Click-Jacking Protection
        • Content Content Security Policy (CSP)
        • Content/MIME Sniffing Protection
        • Cross-site scripting Protection (X-XSS-Protection header)
        • Debug Compilation Mode
        • Excessive Headers
        • Fixed Application Url
        • Folder & File Permissions
        • HTTPS Configuration
        • Notification Email Settings
        • SMTP
        • Strict-Transport-Security Header
    • Language Files & Localization
      • .NET Localization
    • Backoffice Search
    • Creating a Custom Database Table
    • Creating a Custom Seed Key Provider
    • Embedded Media Providers
    • Custom File Systems (IFileSystem)
      • Using Azure Blob Storage for Media and ImageSharp Cache
    • Configuring Azure Key Vault
    • Packages
      • Creating a Package
      • Language file for packages
      • Listing a Package on the Umbraco Marketplace
      • Good practice and defaults
      • Packages on Umbraco Cloud
      • Installing and Uninstalling Packages
      • Maintaining packages
      • Create accessible Umbraco packages
      • Example Package Repository
  • Reference
    • Dive into the code
    • Configuration
      • Basic Authentication Settings
      • Connection strings settings
      • Content Dashboard Settings
      • Content Settings
      • Data Types Settings
      • Debug settings
      • Examine settings
      • Exception filter settings
      • FileSystemProviders Configuration
      • Global Settings
      • Health checks
      • Hosting settings
      • Imaging settings
      • Indexing settings
      • Install Default Data Settings
      • Logging settings
      • Maximum Upload Size Settings
      • Models builder settings
      • Cache Settings
      • Package Migration
      • Plugins settings
      • Request handler settings
      • Runtime settings
      • Security Settings
      • Serilog settings
      • Type finder settings
      • Unattended
      • Web routing
    • Templating
      • Models Builder
        • Introduction
        • Configuration
        • Builder Modes
        • Understand and Extend
        • Using Interfaces
        • Tips and Tricks
      • Working with MVC
        • Working with MVC Views in Umbraco
        • View/Razor Examples
        • Using MVC Partial Views in Umbraco
        • Using View Components in Umbraco
        • Querying & Traversal
        • Creating Forms
      • Macros
    • Querying & Models
      • IMemberManager
      • IPublishedContentQuery
      • ITagQuery
      • UDI Identifiers
      • UmbracoContext helper
      • UmbracoHelper
      • IPublishedContent
        • IPublishedContent Collections
        • IPublishedContent IsHelpers
        • IPublishedContent Property Access & Extension Methods
    • Routing & Controllers
      • Custom MVC controllers (Umbraco Route Hijacking)
      • Custom MVC Routes
      • Custom Middleware
      • URL Rewrites in Umbraco
      • Special Property Type aliases for routing
      • URL Redirect Management
      • Routing in Umbraco
        • FindPublishedContentAndTemplate()
        • IContentFinder
        • Inbound request pipeline
        • Outbound request pipeline
        • Published Content Request Preparation
      • Surface controllers
        • Surface controller actions
      • Umbraco API Controllers
        • Porting old Umbraco API Controllers
    • Content Delivery API
      • Custom property editors support
      • Extension API for querying
      • Media Delivery API
      • Protected content in the Delivery API
        • Server to server access
      • Output caching
      • Property expansion and limiting
      • Additional preview environments support
    • Webhooks
      • Expanding Webhook Events
    • API versioning and OpenAPI
    • Searching
      • Examine
        • Examine Management
        • Examine Manager
        • Custom indexing
        • PDF indexes and multisearchers
        • Quick-start
    • Using Notifications
      • Notification Handler
      • CacheRefresher Notifications Example
      • ContentService Notifications Example
      • Creating And Publishing Notifications
      • Determining if an entity is new
      • MediaService Notifications Example
      • MemberService Notifications Example
      • UserService Notifications Example
      • Sending Allowed Children Notification
      • Umbraco Application Lifetime Notifications
      • EditorModel Notifications
        • Customizing the "Links" box
      • Hot vs. cold restarts
    • Inversion of Control / Dependency injection
    • Management
      • Using Umbraco services
        • Consent Service
        • Media Service
        • Relation Service
        • Content Service
        • Content Type Service
        • Localization Service
        • User Service
    • Plugins
      • Creating Resolvers
      • Finding types
    • Cache & Distributed Cache
      • Cache Seeding
      • Accessing the cache
      • ICacheRefresher
      • IServerMessenger
      • Getting/Adding/Updating/Inserting Into Cache
      • Examples
        • Working with caching
    • Response Caching
    • Security
      • API rate limiting
      • BackOfficeUserManager and Events
      • Cookies
      • Replacing the basic username/password check
      • External login providers
      • Locking of Users and password reset
      • Reset admin password
      • Umbraco Security Hardening
      • Umbraco Security Settings
      • Sensitive data
      • Sanitizing the Rich Text Editor
      • Setup Umbraco for a FIPS Compliant Server
      • HTTPS
      • Two-factor Authentication
      • Server-side file validation
    • Scheduling
    • Common Pitfalls & Anti-Patterns
    • API Documentation
    • Debugging with SourceLink
    • Language Variation
    • UmbracoMapper
    • Distributed Locks
    • Management API
      • External Access
      • Setup OAuth using Postman
    • Custom Swagger API
    • Umbraco Flavored Markdown
    • Content Type Filters
  • Tutorials
    • Overview
    • Creating a Basic Website
      • Getting Started
      • Document Types
      • Creating Your First Template
      • CSS and Images
      • Displaying the Document Type Properties
      • Creating a Master Template
      • Creating Pages and Using the Master Template
      • Setting the Navigation Menu
      • Articles and Article Items
      • Adding Language Variants
      • Conclusions
    • Creating your First Extension
    • Creating a Custom Dashboard
      • Adding localization to the dashboard
      • Adding functionality to the Dashboard
      • Using Umbraco UI library in the Dashboard
    • Creating a Property Editor
      • Adding configuration to a Property Editor
      • Integrating context with a Property Editor
      • Custom value conversion for rendering
      • Adding server-side validation
        • Default Property Editor Schema aliases
    • Creating a Multilingual Site
    • Add Google Authentication (Users)
    • Add Microsoft Entra ID authentication (Members)
    • Creating Custom Database Tables with Entity Framework
    • Migrating Macros
    • The Starter Kit
      • Install the Starter Kit
      • Lessons
        • Customize the Starter Kit
        • Add a Blog Post Publication Date
          • Add a Blog Post Publication Date
          • Add a Blog Post Publication Date
        • Add Open Graph
          • Add Open Graph - Step 1
          • Add Open Graph - Step 2
          • Add Open Graph - Step 3
          • Add Open Graph - Step 4
          • Add Open Graph - Summary
        • Ask For Help and Join the Community
    • Editor's Manual
      • Getting Started
        • Logging In and Out
        • Umbraco Interface
        • Creating, Saving and Publishing Content Options
        • Finding Content
        • Editing Existing Content
        • Sorting Pages
        • Moving a Page
        • Copying a Page
        • Deleting and Restoring Pages
      • Working with Rich Text Editor
      • Version Management
        • Comparing Versions
        • Rollback to a Previous Version
      • Media Management
        • Working with Folders
        • Working with Media Types
        • Cropping Images
      • Tips & Tricks
        • Refreshing the Tree View
        • Audit Trail
        • Notifications
        • Preview Pane Responsive View
        • Session Timeout
    • Multisite Setup
    • Member Registration and Login
    • Custom Views for Block List
    • Connecting Umbraco Forms and Zapier
    • Creating an XML Sitemap
    • Implement Custom Error Pages
    • Create a custom maintenance page
    • Creating a backoffice API
      • Documenting your controllers
      • Adding a custom Swagger document
      • Versioning your API
      • Polymorphic output in the Management API
      • Umbraco schema and operation IDs
      • Access policies
    • Extending the Help Menu
Powered by GitBook
On this page
  • Two-factor authentication for Members
  • Test the set up for Members
  • Notification when 2FA is requested for a member
  • Two-factor authentication for Users
  • Example implementation for Authenticator Apps for Users
  • Test the set up for Users
  • Notification when 2FA is requested for a user
  • Login with 2FA enabled
  • Customizing the 2FA experience
  • Customizing the 2FA activation screen
  • Customizing the login screen

Was this helpful?

Edit on GitHub
Export as PDF
  1. Reference
  2. Security

Two-factor Authentication

Umbraco users and members support a two-factor authentication (2FA) abstraction for implementing a 2FA provider of your choice.

PreviousHTTPSNextServer-side file validation

Last updated 3 months ago

Was this helpful?

This article includes guides for implementing two-factor authentication options for both backoffice users and website members:

Two-factor authentication (2FA) for Umbraco Users and Members is activated by implementing an ITwoFactorProvider interface and registering the implementation. The implementation can use third-party packages to support authentication apps like the Microsoft- or Google Authentication Apps.

If you are using , you can enable multi-factor authentication in Umbraco ID. For more information, see the article.

Two-factor authentication for Members

The following guide will take you through implementing an option for your website members to enable two-factor authentication.

A setup for members needs to be implemented on your website in order for you to follow this guide. This setup should include:

  • Login and logout options.

  • Public access restriction configured on at least 1 content item.

As an example, the guide will use the . This package works for both Google and Microsoft authenticator apps. It can be used to generate the QR code needed to activate the app for the website.

  1. Install the GoogleAuthenticator Nuget Package on your project.

  2. Create a new file in your project: UmbracoAppAuthenticator.cs.

  3. Update the file with the following code snippet.

UmbracoAppAuthenticator.cs
using Google.Authenticator;
using System.Runtime.Serialization;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;

namespace My.Website;

/// <summary>
/// Model with the required data to setup the authentication app.
/// </summary>

[DataContract]
public class QrCodeSetupData : ISetupTwoFactorModel
{
    /// <summary>
    /// The secret unique code for the user and this ITwoFactorProvider.
    /// </summary>
    public string? Secret { get; init; }

    /// <summary>
    /// The SetupCode from the GoogleAuthenticator code.
    /// </summary>
    public SetupCode? SetupCode { get; init; }
}

/// <summary>
/// App Authenticator implementation of the ITwoFactorProvider
/// </summary>
public class UmbracoAppAuthenticator : ITwoFactorProvider
{
    /// <summary>
    /// The unique name of the ITwoFactorProvider. This is saved in a constant for reusability.
    /// </summary>
    public const string Name = "UmbracoAppAuthenticator";

    private readonly IMemberService _memberService;

    /// <summary>
    /// Initializes a new instance of the <see cref="UmbracoAppAuthenticator"/> class.
    /// </summary>
    public UmbracoAppAuthenticator(IMemberService memberService)
    {
        _memberService = memberService;
    }

    /// <summary>
    /// The unique provider name of ITwoFactorProvider implementation.
    /// </summary>
    /// <remarks>
    /// This value will be saved in the database to connect the member with this  ITwoFactorProvider.
    /// </remarks>
    public string ProviderName => Name;

    /// <summary>
    /// Returns the required data to setup this specific ITwoFactorProvider implementation. In this case it will contain the url to the QR-Code and the secret.
    /// </summary>
    /// <param name="userOrMemberKey">The key of the user or member</param>
    /// <param name="secret">The secret that ensures only this user can connect to the authenticator app</param>
    /// <returns>The required data to setup the authenticator app</returns>
    public Task<ISetupTwoFactorModel> GetSetupDataAsync(Guid userOrMemberKey, string secret)
    {
        var member = _memberService.GetByKey(userOrMemberKey);

        var applicationName = "testingOn15";
        var twoFactorAuthenticator = new TwoFactorAuthenticator();
        SetupCode setupInfo = twoFactorAuthenticator.GenerateSetupCode(applicationName, member.Username, secret, false);
        return Task.FromResult<ISetupTwoFactorModel>(new QrCodeSetupData()
        {
            SetupCode = setupInfo,
            Secret = secret
        });
    }

    /// <summary>
    /// Validated the code and the secret of the user.
    /// </summary>
    public bool ValidateTwoFactorPIN(string secret, string code)
    {
        var twoFactorAuthenticator = new TwoFactorAuthenticator();
        return twoFactorAuthenticator.ValidateTwoFactorPIN(secret, code);
    }

    /// <summary>
    /// Validated the two factor setup
    /// </summary>
    /// <remarks>Called to confirm the setup of two factor on the user. In this case we confirm in the same way as we login by validating the PIN.</remarks>
    public bool ValidateTwoFactorSetup(string secret, string token) => ValidateTwoFactorPIN(secret, token);
}
  1. Update namespace on line 7 to match your project.

  2. Customize the applicationName variable on line 64.

  3. Create a Composer and register the UmbracoAppAuthenticator implementation as shown below.

UmbracoAppAuthenticatorComposer.cs
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Security;

namespace My.Website;

public class UmbracoAppAuthenticatorComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        var identityBuilder = new MemberIdentityBuilder(builder.Services);
        identityBuilder.AddTwoFactorProvider<UmbracoAppAuthenticator>(UmbracoAppAuthenticator.Name);
    }
}

At this point, the 2FA is active, but no members have set up 2FA yet. The setup of 2FA depends on the type. In the case of App Authenticator, the view showing the option to edit member profiles needs to be modified.

If you already have a members-only page with the edit profile options, you can skip directly to step 8.

  1. Add or choose a members-only page that should have the two-factor authentication setup.

    • The page needs to be behind the public access.

    • The page should not be using strongly types models.

  2. Open the view file for the selected page.

  3. Add the following code:

ExampleMemberPage.cshtml
@using Umbraco.Cms.Core.Services;
@using Umbraco.Cms.Web.Website.Controllers;
@using Umbraco.Cms.Web.Website.Models;
@using My.Website;
@inject MemberModelBuilderFactory memberModelBuilderFactory
@inject ITwoFactorLoginService twoFactorLoginService
@{
    // Build a profile model to edit
    var profileModel = await memberModelBuilderFactory
        .CreateProfileModel()
        .BuildForCurrentMemberAsync();

    // Show all two factor providers
    var providerNames = twoFactorLoginService.GetAllProviderNames();
    if (providerNames.Any())
    {
        <div asp-validation-summary="All" class="text-danger"></div>
        foreach (var providerName in providerNames)
        {
            var setupData = await twoFactorLoginService.GetSetupInfoAsync(profileModel.Key, providerName);

            // If the `setupData` is `null` for the specified `providerName` it means the provider is already set up.
            // In this case, a button to disable the authentication is shown.
            if (setupData is null)
            {
                @using (Html.BeginUmbracoForm<UmbTwoFactorLoginController>(nameof(UmbTwoFactorLoginController.Disable)))
                {
                    <input type="hidden" name="providerName" value="@providerName"/>
                    <button type="submit">Disable @providerName</button>
                }
            }
            // If `setupData` is not `null` the type is checked and the UI for how to set up the App Authenticator is shown.
            else if(setupData is QrCodeSetupData qrCodeSetupData)
            {
                @using (Html.BeginUmbracoForm<UmbTwoFactorLoginController>(nameof(UmbTwoFactorLoginController.ValidateAndSaveSetup)))
                {
                    <h3>Setup @providerName</h3>
                    <img src="@qrCodeSetupData.SetupCode.QrCodeSetupImageUrl"/>
                    <p>Scan the code above with your authenticator app <br /> and enter the resulting code here to validate:</p>
                    <input type="hidden" name="providerName" value="@providerName"  />
                    <input type="hidden" name="secret" value="@qrCodeSetupData.Secret"  />
                    <input type="text" name="code"  />
                    <button type="submit">Validate & save</button>
                }
            }
        }
    }
}
  1. Update the @using in line 4 to match the namespace of your project.

  2. [Optional] Customize the text fields and buttons to match your websites tone of voice (lines 33-39).

Test the set up for Members

  1. Login to the website using a test member.

  2. Navigate to the page where the QR code was added.

  3. Scan the QR code and add the verification code.

  4. Logout of the website.

  5. Login and verify that it asks for the two factor authentication.

You can also check that the Two-factor Authentication option is checked on the member in the Umbraco backoffice.

Notification when 2FA is requested for a member

Two-factor authentication for Users

The following guide will take you through implementing an option for backoffice users to enable two-factor authentication.

This guide will not cover setting up the UI for user login and edits as this is handled elsewhere in the CMS.

Example implementation for Authenticator Apps for Users

  1. Install the GoogleAuthenticator Nuget Package on your project.

  2. Create a new file in your project: UmbracoUserAppAuthenticator.cs.

  3. Update the file with the following code snippet.

UmbracoUserAppAuthenticator.cs
using System.Runtime.Serialization;
using Google.Authenticator;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;

namespace My.Website;

[DataContract]
public class TwoFactorAuthInfo : ISetupTwoFactorModel
{
    [DataMember(Name = "qrCodeSetupImageUrl")]
    public string? QrCodeSetupImageUrl { get; set; }

    [DataMember(Name = "secret")]
    public string? Secret { get; set; }
}

/// <summary>
/// App Authenticator implementation of the ITwoFactorProvider
/// </summary>
public class UmbracoUserAppAuthenticator : ITwoFactorProvider
{
    /// <summary>
    /// The unique name of the ITwoFactorProvider. This is saved in a constant for reusability.
    /// </summary>
    public const string Name = "UmbracoUserAppAuthenticator";

    private readonly IUserService _userService;

    /// <summary>
    /// Initializes a new instance of the <see cref="UmbracoUserAppAuthenticator"/> class.
    /// </summary>
    public UmbracoUserAppAuthenticator(IUserService userService)
    {
        _userService = userService;
    }

    /// <summary>
    /// Gets the unique provider name of ITwoFactorProvider implementation.
    /// </summary>
    /// <remarks>
    /// This value will be saved in the database to connect the member with this  ITwoFactorProvider.
    /// </remarks>
    public string ProviderName => Name;

    /// <summary>
    /// Returns the required data to setup this specific ITwoFactorProvider implementation. In this case it will contain the url to the QR-Code and the secret.
    /// </summary>
    /// <param name="userOrMemberKey">The key of the user or member</param>
    /// <param name="secret">The secret that ensures only this user can connect to the authenticator app</param>
    /// <returns>The required data to setup the authenticator app</returns>
    public Task<ISetupTwoFactorModel> GetSetupDataAsync(Guid userOrMemberKey, string secret)
    {
        IUser? user = _userService.GetByKey(userOrMemberKey);

        ArgumentNullException.ThrowIfNull(user);

        var applicationName = "My application name";
        var twoFactorAuthenticator = new TwoFactorAuthenticator();
        SetupCode setupInfo = twoFactorAuthenticator.GenerateSetupCode(applicationName, user.Username, secret, false);
        return Task.FromResult<ISetupTwoFactorModel>(new TwoFactorAuthInfo()
        {
            QrCodeSetupImageUrl = setupInfo.QrCodeSetupImageUrl,
            Secret = secret
        });
    }

    /// <summary>
    /// Validated the code and the secret of the user.
    /// </summary>
    public bool ValidateTwoFactorPIN(string secret, string code)
    {
        var twoFactorAuthenticator = new TwoFactorAuthenticator();
        return twoFactorAuthenticator.ValidateTwoFactorPIN(secret, code);
    }

    /// <summary>
    /// Validated the two factor setup
    /// </summary>
    /// <remarks>Called to confirm the setup of two factor on the user. In this case we confirm in the same way as we login by validating the PIN.</remarks>
    public bool ValidateTwoFactorSetup(string secret, string token) => ValidateTwoFactorPIN(secret, token);
}
  1. Update namespace on line 7 to match your project.

  2. Customize the applicationName variable on line 59.

  3. Create a new file in your project: UmbracoUserAppAuthenticatorComposer.cs.

  4. Implement a new composer and register the UmbracoUserAppAuthenticator implementation as shown below.

UmbracoUserAppAuthenticatorComposer.cs
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Security;

namespace My.Website;

public class UmbracoUserAppAuthenticatorComposer : IComposer
{
 public void Compose(IUmbracoBuilder builder)
 {
  var identityBuilder = new BackOfficeIdentityBuilder(builder.Services);

  identityBuilder.AddTwoFactorProvider<UmbracoUserAppAuthenticator>(UmbracoUserAppAuthenticator.Name);
 }
}
  1. Update the namespace on line 4 to match your project.

With the 2FA in place, the provider needs to be registered in the backoffice client so the user can use it.

  1. Add a new file to your project directory: ~/App_Plugins/TwoFactorProviders/umbraco-package.json.

  2. Add the following code to the new file:

~/App_Plugins/TwoFactorProviders/umbraco-package.json
{
  "$schema": "../../umbraco-package-schema.json",
  "name": "2fa providers",
  "version": "1.0.0",
  "extensions": [
    {
      "type": "mfaLoginProvider",
      "alias": "UmbracoUserAppAuthenticator",
      "name": "UmbracoUserAppAuthenticator",
      "forProviderName": "UmbracoUserAppAuthenticator",
      "meta": {
        "label": "Google Authenticator"
      }
    }
  ]
}

At this point, the 2FA is active, but no users have set up 2FA yet.

Test the set up for Users

Each user can now enable the configured 2FA providers on their user.

  1. Access the Umbraco backoffice.

  2. Click the user avatar in the top-right corner.

  1. Select Configure Two-Factor button to get a list of all enabled two-factor providers.

  1. Select Enable to show the configured view.

  1. Follow the instructions to configure 2FA.

When the authenticator is enabled correctly, a disable button is shown instead.

To disable the two-factor authentication on your user, it is required to enter the verification code.

If the code is correct, the provider is disabled.

Notification when 2FA is requested for a user

Login with 2FA enabled

When a user with 2FA enabled logs in, they will be presented with a screen to enter the verification code:

While the 2FA is enabled, the user will be presented with this screen after entering the username and password.

If the code is correct, the user will be logged in. If the code is incorrect, the user will be presented with an error message.

This screen is set up to work well with 2FA providers that require a one-time code to be entered. The code field follows best practices for accessibility in terms of labeling and autocompletion.

A user can have more than one 2FA provider activated simultaneously. In this case, the user will be presented with a dropdown to choose which provider to use before entering a code.

Customizing the 2FA experience

The 2FA experience can be customized in Umbraco. This can be done by creating a custom view for the activation screen and the login screen. This is useful if you have a 2FA provider that requires something else than a one-time code to be entered.

The following examples show how to customize the 2FA activation screen and the 2FA login screen.

The examples are using the @umbraco-cms/backoffice package to get access to the Umbraco backoffice components and services. This package is included in Umbraco and can be used to create custom elements that look and feel like the rest of the Umbraco backoffice.

Customizing the 2FA activation screen

The 2FA activation screen can be customized. This should be done if you have a 2FA provider that does not require a one-time code to be entered.

To customize the 2FA activation screen, you need to create a JavaScript module. The module should export a default custom element to be used in the activation screen. This module should be placed in the App_Plugins/TwoFactorProviders folder.

~/App_Plugins/TwoFactorProviders/2fa-activation.js
import { UserService } from '@umbraco-cms/backoffice/external/backend-api';
import { css, html } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { isApiError, tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';

export default class My2faActivationElement extends UmbLitElement {
    static get properties() {
        return {
            providerName: { type: String },
            displayName: { type: String },
            callback: { type: Function },
            close: { type: Function },
            _loading: { type: Boolean, state: true, attribute: false },
            _qrCodeSetupImageUrl: { type: String, state: true, attribute: false },
            _buttonState: { type: String, state: true, attribute: false },
        };
    }

    constructor() {
        super();

        this.consumeContext(UMB_NOTIFICATION_CONTEXT, (context) => {
            this.notificationContext = context;
        });
    }

    async firstUpdated() {
        await this.#load();
        this._loading = false;
    }

    async #load() {
        if (!this.providerName) {
            this.peek('Provider name is required', 'danger');
            throw new Error('Provider name is required');
        }
        const { data: _data } = await tryExecuteAndNotify(
            this,
            UserService.getUserCurrent2FaByProviderName({ providerName: this.providerName }),
        );
        const data = _data;
        if (!data) {
            this.peek('No data returned', 'danger');
            throw new Error('No data returned');
        }

        // Verify that there is a secret
        if (!data.secret) {
            this.peek('The provider did not return a secret.', 'danger');
            throw new Error('No secret returned');
        }

        this._secret = data.secret;
        this._qrCodeSetupImageUrl = data.qrCodeSetupImageUrl;
    }

    render() {
        if (this._loading) {
            return html`<uui-loader-bar></uui-loader-bar>`;
        }

        return html`
   <uui-form>
    <form id="authForm" name="authForm" @submit=${this.submit} novalidate>
     <umb-body-layout headline=${this.displayName}>
      <div id="main">
       <uui-box .headline=${this.localize.term('member_2fa')}>
        <div class="text-center">
         <p>
          <umb-localize key="user_2faQrCodeDescription">
           Scan this QR code with your authenticator app to enable two-factor authentication
          </umb-localize>
         </p>
         <img
          id="qrCode"
          src=${this._qrCodeSetupImageUrl}
          alt=${this.localize.term('user_2faQrCodeAlt')}
          title=${this.localize.term('user_2faQrCodeTitle')}
          loading="eager" />
        </div>
        <uui-form-layout-item class="text-center">
         <uui-label for="code" slot="label" required>
          <umb-localize key="user_2faCodeInput"></umb-localize>
         </uui-label>
         <uui-input
          id="code"
          name="code"
          type="text"
          inputmode="numeric"
          autocomplete="one-time-code"
          required
          required-message=${this.localize.term('general_required')}
          label=${this.localize.term('user_2faCodeInputHelp')}
          placeholder=${this.localize.term('user_2faCodeInputHelp')}></uui-input>
        </uui-form-layout-item>
       </uui-box>
      </div>
      <div slot="actions">
       <uui-button
        type="button"
        look="secondary"
        .label=${this.localize.term('general_close')}
        @click=${this.close}>
        ${this.localize.term('general_close')}
       </uui-button>
       <uui-button
        .state=${this._buttonState}
        type="submit"
        look="primary"
        .label=${this.localize.term('buttons_save')}>
        ${this.localize.term('general_submit')}
       </uui-button>
      </div>
     </umb-body-layout>
    </form>
   </uui-form>
  `;
    }

    /**
     * Show a peek notification with a message.
     * @param message {String} The message to show.
     * @param color {"positive" | "danger" | undefined} The color of the notification.
     */
    peek(message, color) {
        this.notificationContext.peek(color ?? 'positive', {
            data: {
                headline: this.localize.term('member_2fa'),
                message,
            },
        });
    }

    /**
     * Submit the form with the code and secret back to the opener.
     * @param e {SubmitEvent} The submit event
     */
    async submit(e) {
        e.preventDefault();
        const codeField = this.shadowRoot.getElementById('code');
        codeField?.setCustomValidity('');

        const form = e.target;

        if (!form.checkValidity()) return;

        const formData = new FormData(form);
        const code = formData.get('code');

        if (!code) return;

        this._buttonState = 'waiting';
        const { error } = await this.callback(this.providerName, code, this._secret);

        if (!error) {
            this.peek(this.localize.term('user_2faProviderIsEnabledMsg', this.displayName ?? this.providerName));
            this._buttonState = 'success';
            this.close();
        } else {
            this._buttonState = 'failed';
            if (isApiError(error)) {
                if (error.body?.operationStatus === 'InvalidCode') {
                    codeField?.setCustomValidity(this.localize.term('user_2faInvalidCode'));
                    codeField?.focus();
                } else {
                    this.peek(
                        this.localize.term('user_2faProviderIsNotEnabledMsg', this.displayName ?? this.providerName),
                        'warning',
                    );
                }
            } else {
                this.peek(error.message, 'warning');
            }
        }
    }

    static get styles() {
        return [
            UmbTextStyles,
            css`
                #authForm {
                    height: 100%;
                }

                #qrCode {
                    width: 100%;
                    aspect-ratio: 1;
                }

                #code {
                    width: 100%;
                    max-width: 300px;
                }

                .text-center {
                    text-align: center;
                }
            `,
        ];
    }
}

customElements.define('my-2fa-activation', My2faActivationElement);

This module will show a QR code and an input field for the user to enter the code from the authenticator app. When the user submits the form, the code will be sent to the server to validate. If the code is correct, the provider will be enabled.

To replace the default activation screen with the custom view, you need to register the element in the umbraco-package.json file that you created before. The final form of the file should look like this:

~/App_Plugins/TwoFactorProviders/umbraco-package.json
{
  "$schema": "../../umbraco-package-schema.json",
  "name": "2fa providers",
  "version": "1.0.0",
  "extensions": [
    {
      "type": "mfaActivationProvider",
      "alias": "UmbracoUserAppAuthenticator",
      "name": "UmbracoUserAppAuthenticator",
      "forProviderName": "UmbracoUserAppAuthenticator",
      "element": "/App_Plugins/TwoFactorProviders/2fa-activation.js", // This line is the only change
      "meta": {
        "label": "Google Authenticator"
      }
    }
  ]
}

Customizing the login screen

The 2FA login screen can also be customized. This should be done if you have a 2FA provider that requires something else than a one-time code to be entered.

You should only customize the 2FA login screen in certain cases, for example:

  • If you have a provider that requires a non-numeric field or additional info.

  • If you have a provider that requires the user to scan a QR code, you should additionally show the QR code.

  • If you need to authenticate the user in a different way than the default option.

You need to create a JavaScript module that exports a default custom element to be used in the login screen. This module should be placed in the App_Plugins folder. The module should be registered using a composer.

You can use the following code as a starting point. This will give you a view looking like this, where the user can enter a code and click a button to verify the code. This is similar to the built-in view in Umbraco. In a real world scenario, you would probably want to authenticate the user in a different way.

The element registers two properties: providers and returnPath. These properties are used to render the view. The providers property is an array of strings, where each string is the name of a 2FA provider. The returnPath is the path to redirect to after a successful login. Both supplied by the login screen automatically.

~/App_Plugins/TwoFactorProviders/Custom2faLogin.js
import { css, html } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';

export default class My2faViewElement extends UmbLitElement {
  static get properties() {
    return {
      providers: { type: Array },
      returnPath: { type: String },
      buttonState: { type: String, state: true, attribute: false }
    };
  }

  get codeField() {
    return this.shadowRoot.getElementById('code');
  }

  /**
   * @param evt {SubmitEvent}
   * @param provider {String}
   * @returns {Promise<void>}
   */
  async onSubmit(evt, provider) {
    evt.preventDefault();

    this.codeField.error = false;
    this.codeField.setCustomValidity('');
    this.errorMessage = '';

    /**
     * @type {HTMLFormElement}
     */
    const form = evt.target;

    const isValid = form.checkValidity();
    if (!isValid) {
      return;
    }

    this.buttonState = 'loading';
    const formData = new FormData(form);
    const code = formData.get('code');

    const authContext = await this.getContext('UmbAuthContext');
    if (!authContext) {
      this.errorMessage = 'Error: No auth context';
      this.buttonState = 'failed';
      return;
    }

    const { error } = await authContext.validateMfaCode(code, provider);

    if (error) {
      this.codeField.error = true;
      this.codeField.errorMessage = error;
      this.codeField.focus();
      this.buttonState = 'failed';
      return;
    }

    this.buttonState = 'success';

    if (this.returnPath) {
      window.location.href = this.returnPath;
    }
  }

  renderProvider(provider) {
    return html`
      <uui-form>
        <form method="post" @submit=${(e) => this.onSubmit(e, provider)} novalidate>
          <h3>${provider}</h3>
          <p>You are about to sign-in with ${provider}.</p>
          <uui-form-layout-item>
            <uui-label for="code" slot="label" required>Type the authentication code from your device</uui-label>
            <uui-input id="code" type="text" name="code" autocomplete="one-time-code" inputmode="numeric" placeholder="123456" required>
              <div slot="prepend">
                <uui-icon name="wand"></uui-icon>
              </div>
            </uui-input>
          </uui-form-layout-item>

          <div>
            <uui-button type="submit" id="button" look="primary" .state=${this.buttonState}>
              <uui-icon name="icon-cloud"></uui-icon>
              Authenticate
            </uui-button>
          </div>

          <div id="error">
            ${this.errorMessage}
          </div>
        </form>
      </uui-form>
    `;
  }

  render() {
    return html`
      ${this.providers.length ? this.providers.map(provider => this.renderProvider(provider)) : html`<p>Error: No providers available</p>`}
    `;
  }

  static styles = css`
    :host {
      display: block;
      width: 100%;
    }
    #button {
      width: 100%;
    }
    #error {
      color: red;
    }
  `;
}

customElements.define('my-2fa-view', My2faViewElement);

We need to register the custom view using a composer. This can be done on the IUmbracoBuilder in your startup or a composer. In this case, we will add a composer to your project. This composer will overwrite the IBackOfficeTwoFactorOptions to use the custom view.

TwoFactorConfiguration.cs
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Web.BackOffice.Security;

namespace My.Website;

/// <inheritdoc />
public class TwoFactorConfiguration : IBackOfficeTwoFactorOptions
{
    /// <inheritdoc />
    public string GetTwoFactorView(string username) => "/App_Plugins/TwoFactorProviders/Custom2faLogin.js";
}

public class TwoFactorConfigurationComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddSingleton<IBackOfficeTwoFactorOptions, TwoFactorConfiguration>();
    }
}

When a 2FA login is requested for a member, the MemberTwoFactorRequestedNotification is published. This notification can also be used to send the member a one-time password via e-mail or phone. Even though these 2FA types are as App Authentication, it is still a massive improvement compared to no 2FA.

As an example, the guide will use the . This package works for both Google and Microsoft authenticator apps. It can be used to generate the QR code needed to activate the app for the website.

When a 2FA login is requested for a user, the UserTwoFactorRequestedNotification is published. This notification can also be used to send the user a one-time password via e-mail or phone. Even though these 2FA types are as App Authentication, it is still a massive improvement compared to no 2FA.

The examples are using the library to create custom elements. This is the recommended way of creating custom elements in Umbraco. Lit is a light-weight library that augments the to provide a declarative, performant, and interoperable way to create web components.

They are written in vanilla JavaScript and C#, but the same principles can be applied to other languages. For more information about creating custom elements in Umbraco with a bundler and TypeScript, see the article.

The following code is an example of a custom 2FA login screen using . This is the recommended way of creating a custom 2FA login screen. Lit is a light-weight library that augments the to provide a declarative, performant, and interoperable way to create web components.

not considered secure
GoogleAuthenticator NuGet Package
not considered secure
Lit
Custom Elements API
Development Flow
Lit
Custom Elements API
Umbraco Cloud
Multi-Factor Authentication
Learn more about setting up a members section in Umbraco.
GoogleAuthenticator NuGet Package
Two-Factor Authentication for Members
Two-Factor Authentication for Users
The QR Code is shown along with a field to enter a value to set up the two factor authentication.
Check the Member profile in the Umbraco backoffice to verify whether two-factor authentication is enabeld.
User panel
Configure 2fa
Enable 2fa
Disable 2fa
Verify disable
Default 2FA login
Custom 2FA login