DEV Community

Cover image for Umbraco 13 - Setting The Backoffice Login Background & Logo From Content Data
Andy Boot
Andy Boot

Posted on

Umbraco 13 - Setting The Backoffice Login Background & Logo From Content Data

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

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

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:

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

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

Image description

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

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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 => { }
            ));
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Drumroll...

Image description

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
👋

Top comments (0)