Creating an Umbraco Backoffice Accessor for Conditional Page Rendering
Tags: webdev, umbraco, filter, tutorial
Published: 2024-07-06 07:42:41

## 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](https://shekhartarare.com/Archive/2024/6/how-to-create-a-new-umbraco-project) 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; }
    }
}
```

> **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;
            }
        }
    }
}
```

> **Explanation**:
> - **Class Declaration**: BackofficeUserAccessor class implements the IBackofficeUserAccessor interface.
> - **Constructor**: The constructor accepts IOptionsSnapshot<CookieAuthenticationOptions> 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();
```

## 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();
        }
    }
}
```

> **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>
}
```

> **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](https://github.com/ShekharTarare/Umbraco-Backoffice-Accessor). 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.