DEV Community

Cover image for Creating an Umbraco Backoffice Accessor for Conditional Page Rendering
Shekhar Tarare
Shekhar Tarare

Posted on • Originally published at shekhartarare.com

Creating an Umbraco Backoffice Accessor for Conditional Page Rendering

Introduction

Umbraco is a popular CMS that offers extensive customization and security features. One of the critical aspects of managing an Umbraco site is ensuring that only authenticated users can access certain parts of the site. In this guide, we'll create an accessor to check the user's authentication status in the Umbraco backoffice and use it in a view to conditionally display content.

Prerequisites

  • Basic knowledge of ASP.NET Core.
  • An existing Umbraco installation. (Check this blog to create a new umbraco 13 project)
  • Visual Studio or any other C# IDE.

Setting Up the Umbraco Backoffice User Accessor

First, we need to create an accessor that checks the user's authentication status in the backoffice.

Step 1: Define the IBackofficeUserAccessor Interface
This interface exposes the authentication status of the backoffice user

using System.Security.Claims;
namespace YourNamespace
{
    public interface IBackofficeUserAccessor
    {
        ClaimsPrincipal BackofficeUser { get; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:
This code defines an interface called IBackofficeUserAccessor with a property BackofficeUser of type ClaimsPrincipal. This interface will be implemented to access the user's claims.

Step 2: Implement the BackofficeUserAccessor Class
This class implements the IBackofficeUserAccessor interface to retrieve the backoffice user's authentication status. There are some extra logging added to log errors.

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using System.Security.Claims;
namespace YourNamespace
{
    public class BackofficeUserAccessor : IBackofficeUserAccessor
    {
        private readonly IOptionsSnapshot<CookieAuthenticationOptions> _cookieOptionsSnapshot;
        private readonly IHttpContextAccessor _httpContextAccessor;

        private readonly ILogger<BackofficeUserAccessor> _logger;
        public BackofficeUserAccessor(
            IOptionsSnapshot<CookieAuthenticationOptions> cookieOptionsSnapshot,
            IHttpContextAccessor httpContextAccessor,
            ILogger<BackofficeUserAccessor> logger
        )
        {
            _cookieOptionsSnapshot = cookieOptionsSnapshot;
            _httpContextAccessor = httpContextAccessor;
            _logger = logger; 
        }

        public ClaimsIdentity BackofficeUser
        {
            get
            {
                var httpContext = _httpContextAccessor.HttpContext;
                if (httpContext == null)
                {
                    _logger.LogWarning("BackofficeAUserAccessor: HttpContext is null.");
                    return new ClaimsIdentity();
                }

                CookieAuthenticationOptions cookieOptions = _cookieOptionsSnapshot.Get(Umbraco.Cms.Core.Constants.Security.BackOfficeAuthenticationType);

                string? backOfficeCookie = httpContext.Request.Cookies[cookieOptions.Cookie.Name!];
                if (string.IsNullOrEmpty(backOfficeCookie))
                {
                    _logger.LogWarning("BackofficeAUserAccessor: BackOffice cookie is null or empty.");
                    return new ClaimsIdentity();
                }
                AuthenticationTicket? unprotected;
                try
                {
                    unprotected = cookieOptions.TicketDataFormat.Unprotect(backOfficeCookie!);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "BackofficeAUserAccessor: Failed to unprotect the BackOffice cookie.");
                    return new ClaimsIdentity();
                }
                if (unprotected == null)
                {
                    _logger.LogWarning("BackofficeAUserAccessor: Unprotected authentication ticket is null.");
                    return new ClaimsIdentity();
                }
                ClaimsIdentity? backOfficeIdentity = unprotected.Principal.GetUmbracoIdentity();
                if (backOfficeIdentity == null)
                {
                    _logger.LogWarning("BackofficeAUserAccessor: BackOffice identity is null.");
                }
                else
                {
                    _logger.LogInformation("BackofficeAUserAccessor: User authenticated.");
                }
                return backOfficeIdentity;
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Class Declaration: BackofficeUserAccessor class implements the IBackofficeUserAccessor interface.
  • Constructor: The constructor accepts IOptionsSnapshot and IHttpContextAccessor to access cookie options and the current HTTP context.
  • BackofficeUser Property: This property checks if the current HTTP context is null. If not, it retrieves the backoffice authentication cookie and checks if it's empty. If the cookie is valid, it unprotects the cookie and retrieves the user's claims principal. If not, it returns an empty claims principal.

Step 3: Register the Accessor in Program.cs
Register the BackofficeUserAccessor in the dependency injection container.

using InstallingUmbracoDemo;
using Umbraco.Cms.Core.Services;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Register IHttpContextAccessor
builder.Services.AddHttpContextAccessor();
// Register the BackofficeUserAccessor
builder.Services.AddTransient<IBackofficeUserAccessor, BackofficeUserAccessor>();

builder.CreateUmbracoBuilder()
    .AddBackOffice()
    .AddWebsite()
    .AddDeliveryApi()
    .AddComposers()
    .Build();
WebApplication app = builder.Build();
await app.BootUmbracoAsync();
app.UseUmbraco()
    .WithMiddleware(u =>
    {
        u.UseBackOffice();
        u.UseWebsite();
    })
    .WithEndpoints(u =>
    {
        u.UseInstallerEndpoints();
        u.UseBackOfficeEndpoints();
        u.UseWebsiteEndpoints();
    });
await app.RunAsync();
Enter fullscreen mode Exit fullscreen mode

Using the Backoffice User Accessor in a Controller and View

We can now use the BackofficeUserAccessor directly in our controller and views to conditionally render content.

Use BackofficeUserAccessor in a Controller
Inject the IBackofficeUserAccessor into your controller to check the user's authentication status.

YourController.cs:

using Microsoft.AspNetCore.Mvc;
namespace YourNamespace.Controllers
{
    public class YourController : Controller
    {
        private readonly IBackofficeUserAccessor _backofficeUserAccessor;
        public YourController(IBackofficeUserAccessor backofficeUserAccessor)
        {
            _backofficeUserAccessor = backofficeUserAccessor;
        }
        public IActionResult YourAction()
        {
            if (!_backofficeUserAccessor.BackofficeUser.IsAuthenticated)
            {
                return Unauthorized("You are not authorized to view this page.");
            }
            return View();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Constructor Injection: The YourController class receives an instance of IBackofficeUserAccessor via its constructor.
  • Authentication Check: In the YourAction method, the user's authentication status is checked using _backofficeUserAccessor. BackofficeUser. IsAuthenticated. If the user is not authenticated, it returns an Unauthorized result; otherwise, it renders the view.

Use BackofficeUserAccessor in a View
Use the IBackofficeUserAccessor directly in your Razor view to conditionally render content.

YourView.cshtml:

@inject YourNamespace.IBackofficeUserAccessor BackofficeUserAccessor
@if (BackofficeUserAccessor.BackofficeUser.IsAuthenticated)
{
    <h1>Welcome, authenticated user!</h1>
    <!-- Your protected content goes here -->
}
else
{
    <h1>You are not authorized to view this content.</h1>
}
Enter fullscreen mode Exit fullscreen mode

Explanation:
Dependency Injection in View: The BackofficeUserAccessor is injected into the view using the @inject directive.
Conditional Rendering: The view checks if the user is authenticated using BackofficeUserAccessor. BackofficeUser. IsAuthenticated. If the user is authenticated, it displays the protected content; otherwise, it shows an unauthorized message.

Complete code:

You can check the complete code here. I have used Umbraco 13.4.0 in the project.

Conclusion

By following these steps, you have created a backoffice user accessor that checks if a user is logged into the Umbraco backoffice and used it to conditionally render content in your views. This ensures that only authenticated users can access specific parts of your site, enhancing its security.

Top comments (0)