Umbraco 13 - Setting The Backoffice Login Background & Logo From Content Data
Tags: umbraco, dotnet, dotnetcore, webdev
Published: 2024-05-15 16:16:47

The release of Umbraco 13 back in December 2023 brought with it a whole new look and refreshed login screen based on the same technology as "Bellissima" (the upcoming Umbraco 14 backoffice revamp).

Out of the box, it looks like this:
![Umbraco 13 login screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p68lkj5xvxtx63xla8vn.png)

## Introduction
Umbraco offers a way to customise the logo and image by setting alternative values against the `Umbraco:CMS:Content:LoginBackgroundImage` and `Umbraco:CMS:Content:LoginLogoImage` properties within `appsettings.json`:

![appsettings.json showing example LoginBackgroundImage and LoginLogoImage values](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vd9oznnb08xbs844usp1.png)

> Check out **Warren Buckley**'s blog post here for a further breakdown of how this works: https://blog.hackmakedo.com/2023/11/20/quick-tip-how-to-customise-your-umbraco-13-login-screen/  

After watching a recent video uploaded by **Paul Seal**, it inspired me to expand on the idea he presented. Paul demo's how to achieve a similar desired effect by providing an element of customisability from the Umbraco backoffice by utilising the Umbraco package Skybrud Redirects. Check it out here:

{% embed https://www.youtube.com/watch?v=nhlGtP3QSjE %}

## My approach
Using a similar methodology to Paul's approach, I'm going to show you how to do a code only implementation, without the need for an additional package dependency.

> Before I begin, I structure my content tree so that the root page holds all the global settings for the website. Some developers prefer to have a separate settings document type held as a child node (which is also perfectly acceptable). If this is you, you'll need to adapt parts of the code below around your setup.

First, we're going to create two new properties against the root document type:

**_Name:_** Umbraco Custom Login Background Image
**_Alias:_** umbracoCustomLoginBackground
**_Description:_** Suggested image size: 936x1404 pixels (JPG)
**_Data type:_** Media Picker

**_Name:_** Umbraco Custom Login Logo Image
**_Alias:_** umbracoCustomLoginLogo
**_Description:_** Suggested image size: 300x82 pixels (SVG or PNG)
**_Data type:_** Media Picker

Like so:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ettxdgfmia6keiz6p03z.png)

Next, we're going to populate our new fields with some appropriate images from the media library:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t7ngovpn46j899dmq13w.png)

Now we just need to wire this up with a small piece of middleware:

```csharp
public class UmbracoLoginImageMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IRuntimeState _runtimeState;
    private readonly IUmbracoContextFactory _umbracoContextFactory;

    private const string DEFAULT_UMBRACO_LOGIN_BACKGROUND_IMAGE = "/umbraco/assets/img/login.jpg";
    private const string DEFAULT_UMBRACO_LOGIN_LOGO_IMAGE = "/umbraco/assets/img/application/umbraco_logo_blue.svg";

    private const string LOGIN_BACKGROUND_IMAGE_PROPERTY_ALIAS = "umbracoCustomLoginBackground";
    private const string LOGIN_LOGO_IMAGE_PROPERTY_ALIAS = "umbracoCustomLoginLogo";

    public UmbracoLoginImageMiddleware(RequestDelegate next,
        IRuntimeState runtimeState,
        IUmbracoContextFactory umbracoContextFactory)
    {
        _next = next;
        _runtimeState = runtimeState;
        _umbracoContextFactory = umbracoContextFactory;
    }

    public async Task Invoke(HttpContext context)
    {
        if (_runtimeState.Level != RuntimeLevel.Run)
        {
            await _next.Invoke(context);
            return;
        }

        var pathAndQuery = context.Request.GetEncodedPathAndQuery();

        if (pathAndQuery.InvariantIndexOf(DEFAULT_UMBRACO_LOGIN_BACKGROUND_IMAGE) != 0 && pathAndQuery.InvariantIndexOf(DEFAULT_UMBRACO_LOGIN_LOGO_IMAGE) != 0)
        {
            await _next(context);
            return;
        }

        // Handle the background or logo image
        if (!HandleLoginImageRedirect(context, DEFAULT_UMBRACO_LOGIN_BACKGROUND_IMAGE, LOGIN_BACKGROUND_IMAGE_PROPERTY_ALIAS) && 
            !HandleLoginImageRedirect(context, DEFAULT_UMBRACO_LOGIN_LOGO_IMAGE, LOGIN_LOGO_IMAGE_PROPERTY_ALIAS))
        {
            await _next(context);
        }
    }

    private bool HandleLoginImageRedirect(HttpContext context, string defaultImage, string propertyAlias)
    {
        // If the current request URL equals the default path or what we have in appsettings.json
        if (context.Request.Path.Value?.InvariantEquals(defaultImage) == true)
        {
            using var ctx = _umbracoContextFactory.EnsureUmbracoContext();

            var contentAtRoot = ctx.UmbracoContext.Content?.GetAtRoot(); // Get all document nodes at the root level of our content
            var firstRootContent = contentAtRoot?.FirstOrDefault(); // Get the first one (adjust this if you want to pull through something specific)

            if (firstRootContent != null && firstRootContent.HasValue(propertyAlias)) // If our sites root page has a matching property with a value
            {
                var mediaItemUrl = firstRootContent.Value<IPublishedContent>(propertyAlias)?.Url(); // Get & parse the media library item to get the direct URL

                if (!string.IsNullOrWhiteSpace(mediaItemUrl))
                {
                    context.Response.Redirect(mediaItemUrl, true); // Redirect to the media set in the backoffice
                    return true;
                }
            }
        }

        return false;
    }
}
```

Finally, we just need to register it from within a composer:

```csharp
public class MyComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.Configure<UmbracoPipelineOptions>(options =>
        {
            options.AddFilter(new UmbracoPipelineFilter(
                "UmbracoLoginImageMiddleware",
                applicationBuilder =>
                {
                    applicationBuilder.UseMiddleware<UmbracoLoginImageMiddleware>();
                },
                applicationBuilder =>
                {
                },
                applicationBuilder => { }
            ));
        });
    }
}
```

Drumroll...

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c6h7hdzhn86ekrzg1mua.png)

## Summary
The middleware finds the first root page and attempts to get values from the two newly created media picker properties. If the either of the two image HTTP request matches either of the default image URL's and the adjacent property has a value then it'll perform a permanent 301 redirect to your media library image instead.

While we redirect the background image from `/umbraco/assets/img/login.jpg`, it's probably a safe bet that this wouldn't harm any other component or page within the backoffice. However, overriding `/umbraco/assets/img/application/umbraco_logo_blue.svg` gives me a little more concern as it may be used elsewhere. On initial inspection I can't see anywhere specific where this is also defined so we may be able to get away with it. If however it does reveal itself to be used for other things we can workaround this by re-saving the SVG to somewhere within your `wwwroot` folder, setting the `Umbraco:CMS:Content:LoginLogoImage` value and updating the `DEFAULT_UMBRACO_LOGIN_LOGO_IMAGE` const within the middleware.

Please be aware that frequent changing of redirects against a URL may result in the redirect being cached and therefore you won't always see your changes immediately. To overcome this, disable browser caching.

I hope you find this article interesting. I wanted to share this approach with you because I hadn't seen it done yet.

Thanks for reading,
Andy
👋