Automatically Adding Member Groups to Umbraco Public Access
23rd February, 2025
How to use middleware to enforce/add a default Member group to group-based Public Access restrictions in Umbraco.
Recently, I worked on a project that used Umbraco's Public Access Restrictions to give frontend Members access to different parts of the site. Additionally, the members were split into different groups and, therefore, had access to different areas depending on which group they were in.
As part of this, the client required an "Administrators" member group, so that non-backoffice users could moderate the user-generated content in these group-restricted frontend pages. And, as these groups and group-restricted areas were going to be regularly created, updated, and deleted, I needed to figure out a way to allow access for the "Administrators" group in a blanket manner.
Unfortunately, Public Access in Umbraco is quite limited, there's no out-of-the-box way to setup default allowed Member Groups, set a default login page or even a default access denied page... so if you're content editors need to set public access regularly, then adding these every time is honestly a bit of a bit of a pain as you'll need to select these each time you want to enable Public Access restrictions on a node.
How do I automatically include a group when a content editor sets up Public Access?
As there's no Notification that I could use to hook into the saving of public access, like we can with content saving etc.., I instead created some Middleware to intercept the Public Access save request; and inject my group that way:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using System.Text; namespace MyProject.Web.Middleware { public class PublicAccessMiddleware { private readonly RequestDelegate _next; public PublicAccessMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { if (context.Request.Path.StartsWithSegments("/umbraco/backoffice/umbracoapi/publicaccess/PostPublicAccess") == false) { await _next(context); return; } // Read the query string parameters var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(context.Request.QueryString.Value); // Check if "groups[]" exists in the query string if (query.TryGetValue("groups[]", out StringValues values) == false) { await _next(context); return; } // Check if we are setting any groups var groups = values.ToArray(); if (groups.Any() == false) { await _next(context); return; } // Check if Admins are already included var pos = Array.IndexOf(groups, "Administrators"); if (pos > -1) { await _next(context); return; } // Modify the groups[] value to include Admins Array.Resize(ref groups, groups.Length + 1); groups[^1] = "Administrators"; // Create a new query string with the modified groups[] var modifiedQuery = new Dictionary<string, StringValues>(query) { ["groups[]"] = new StringValues(groups) }; // Rebuild the query string var newQueryString = new StringBuilder("?"); foreach (var kvp in modifiedQuery) { foreach (var value in kvp.Value) { newQueryString.Append($"{kvp.Key}={Uri.EscapeDataString(value)}&"); } } // Update the request with the modified query string context.Request.QueryString = new QueryString(newQueryString.ToString().TrimEnd('&')); await _next(context); } } public static class PublicAccessMiddlewareExtensions { /// <summary> /// Registers the Public Access Middleware which will intercept requests to the /// "/umbraco/backoffice/umbracoapi/publicaccess/PostPublicAccess" endpoint and inject /// the Administrators Member group if it is not present in the list of groups. /// </summary> /// <param name="app"></param> public static void RegisterPublicAccessMiddleware(this WebApplication app) { app.UseWhen(context => context.Request.Path.StartsWithSegments("/umbraco/backoffice/umbracoapi/publicaccess/PostPublicAccess"), appBuilder => appBuilder.UseMiddleware<PublicAccessMiddleware>()); } } }
As you can see in the above code snippets, I intercept the Public Access save request, check whether we're saving groups, check whether the Administrators group is already included, and add it to the query string if not.
Tip: I would recommend sticking the group name in a "constants" class so that you only need to change it in one place if needed.
namespace MyProject.Models.Constants { public static class MemberGroupConstants { public const string Admin = "Administrators"; } }
Now all we need to do is call our registration code in our Program.cs, before, and off we go!
//... app.RegisterPublicAccessMiddleware(); app.UseUmbraco() //....
Demo:

And there we go, now my content editors don't have to remember to add the Admin group manually every time!
Now, I just need to figure out how to automatically include the login and access denied pages... 🤔