Editor User Sees and Accesses Members Section After Page Refresh (Umbraco 10)

Hi everyone,

I’m facing a strange issue in Umbraco 10 CMS related to user permissions.

I have an Editor user who has been assigned the following user group permissions:

  • Editor
  • Sensitive data

The intention is to allow this user to access only:

  • Content
  • Media
  • Translation

However, when I log in as this Editor user and navigate to either the Media or Translation tab and then do a page refresh (Ctrl+R or F5), the Members section suddenly appears in the top navigation bar. What’s more concerning is that the Editor can access the Members section and even make changes there.

This seems to bypass the role-based access setup and exposes sensitive parts of the backoffice that should only be available to Admins.

Has anyone else come across this issue?
Is this a known bug in Umbraco 10 or is there any workaround/fix for this?

Appreciate any help or guidance!

Thanks,
Anju

What happends if you use a incognito window to do this or clear all your cache? I’m just wondering if it’s some kind of cache because you probably log in as a administrator first and then try again with the editor, maybe even in the same browser.

Umbraco 10 is completely end of life so even if it is a bug, it won’t be fixed in 10.

I have tried it on incognito. The issue is still exist

Hi everyone,

Thanks for reading and supporting the thread!

I managed to resolve the issue with the Members section appearing for non-admin users after a page refresh in Umbraco 10.

:wrench: What I did:

I implemented a custom AngularJS extension to continuously monitor the URL and DOM in the Umbraco backoffice. Here’s a summary of the approach:

  1. Created a custom Angular module (custom.memberTabControl) that:
  • Fetches the current user using authResource.
  • Determines if the user is an admin (based on user ID, groups, userType, or allowed sections).
  • Hides the Members section entirely for non-admin users.
  • Redirects to the Content section if a non-admin tries to access /member directly or via refresh.
  • Uses $rootScope route/location/state change listeners and MutationObserver to dynamically handle tab reappearances.
  • Implements periodic and continuous checks for added safety.
  1. Added this module via a package.manifest so it loads with the Umbraco backoffice.
(function () {
    'use strict';
    angular.module('umbraco').requires.push('custom.memberTabControl');
    angular.module('custom.memberTabControl', [])
        .run(['authResource', '$rootScope', '$timeout', '$interval', '$location',
            function (authResource, $rootScope, $timeout, $interval, $location) {

                // CONTINUOUS URL MONITORING - Block member access immediately and continuously
                let isAuthCheckComplete = false;
                let isAdminUser = false;

                function immediateBlockMemberAccess() {
                    const currentUrl = window.location.href.toLowerCase();
                    if (currentUrl.includes('/umbraco#/member') || currentUrl.includes('/umbraco/#/member')) {
                        // Only block if we know the user is not an admin
                        if (isAuthCheckComplete && !isAdminUser) {
                            console.warn('Non-admin user tried to access member section. Redirecting to content...');
                            window.location.href = '/umbraco#/content';
                            return true;
                        } else if (!isAuthCheckComplete) {
                            // If auth check is not complete, redirect as a safety measure
                            console.warn('Potential unauthorized member access detected. Redirecting to content...');
                            window.location.href = '/umbraco#/content';
                            return true;
                        }
                    }
                    return false;
                }

                // Set up continuous URL monitoring
                function setupContinuousUrlMonitoring() {
                    // Monitor URL changes via hash change
                    window.addEventListener('hashchange', function () {
                        immediateBlockMemberAccess();
                    });

                    // Monitor URL changes via popstate
                    window.addEventListener('popstate', function () {
                        immediateBlockMemberAccess();
                    });

                    // Periodic URL checking
                    const urlCheckInterval = setInterval(() => {
                        immediateBlockMemberAccess();
                    }, 100); // Check every 100ms

                    // Clean up after 5 minutes
                    setTimeout(() => {
                        clearInterval(urlCheckInterval);
                    }, 300000);
                }

                // Start continuous monitoring
                setupContinuousUrlMonitoring();

                // Run immediate check first
                if (immediateBlockMemberAccess()) {
                    return; // Exit if we already redirected
                }

                // Also hide any member content immediately (before auth check)
                function immediateHideMemberContent() {
                    const memberSelectors = [
                        'li[data-element="section-member"]',
                        'li[data-element="section-members"]',
                        'a[href*="#/member"]',
                        'a[href*="member"]',
                        '.umb-navigation__link[href*="member"]',
                        '.umb-nav-item[data-element="section-member"]',
                        '.umb-navigation li:has(a[href*="member"])'
                    ];

                    memberSelectors.forEach(selector => {
                        try {
                            const elements = document.querySelectorAll(selector);
                            elements.forEach(element => {
                                if (element && !element.closest('.umb-user-profile') && !element.closest('.umb-user')) {
                                    element.style.display = 'none !important';
                                    element.style.visibility = 'hidden';
                                    element.style.opacity = '0';
                                }
                            });
                        } catch (e) {
                            // Ignore selector errors
                        }
                    });
                }

                // Hide member content immediately
                immediateHideMemberContent();

                authResource.getCurrentUser().then(function (user) {
                    console.log("Full user object:", user);

                    // Multiple ways to detect admin users
                    const groups = user?.userGroups || [];
                    const userType = user?.userType || '';
                    const allowedSections = user?.allowedSections || [];

                    console.log("User groups:", groups);
                    console.log("User type:", userType);
                    console.log("Allowed sections:", allowedSections);

                    // Check if user is admin through specific methods
                    let isAdmin = false;

                    // Method 1: Check if user ID is 0 (super admin) or -1 (system user)
                    if (user.id === 0 || user.id === -1) {
                        isAdmin = true;
                        console.log("Admin detected by user ID:", user.id);
                    }

                    // Method 2: Check user groups for specific admin groups only
                    if (!isAdmin && groups.length > 0) {
                        isAdmin = groups.some(g => {
                            const groupName = (typeof g === 'string' ? g : (g.alias || g.name || '')).toLowerCase();
                            return groupName === 'administrator' || groupName === 'administrators';
                        });
                        if (isAdmin) {
                            console.log("Admin detected by user group");
                        }
                    }

                    // Method 3: Check if user type is specifically admin
                    if (!isAdmin && userType.toLowerCase() === 'admin') {
                        isAdmin = true;
                        console.log("Admin detected by user type:", userType);
                    }

                    // Method 4: Check if user has all major sections (content, media, settings, users, members)
                    if (!isAdmin && allowedSections.length >= 5) {
                        const requiredSections = ['content', 'media', 'settings', 'users', 'member'];
                        const hasAllSections = requiredSections.every(section =>
                            allowedSections.includes(section)
                        );
                        if (hasAllSections) {
                            isAdmin = true;
                            console.log("Admin detected by having all sections");
                        }
                    }

                    console.log("Final admin status:", isAdmin);

                    // Set the auth check completion status
                    isAuthCheckComplete = true;
                    isAdminUser = isAdmin;

                    if (isAdmin) {
                        console.log('Admin user detected – allowing member tab access.');
                        // For admin users, show the member tab if it was hidden
                        showMemberTabForAdmin();
                        return;
                    }

                    // Enhanced blocking for non-admins
                    function preventDirectMemberAccess() {
                        const currentUrl = window.location.href.toLowerCase();
                        const currentPath = $location.path().toLowerCase();

                        if (currentUrl.includes('/umbraco#/member') ||
                            currentUrl.includes('/umbraco/#/member') ||
                            currentPath.includes('/member')) {
                            console.warn('Non-admin user tried to access /member directly. Redirecting...');
                            $location.path('/content');
                            return;
                        }
                    }

                    // Call this immediately after auth check
                    preventDirectMemberAccess();

                    // Function to show member tab for admin users
                    function showMemberTabForAdmin() {
                        const memberSelectors = [
                            'li[data-element="section-member"]',
                            'li[data-element="section-members"]',
                            'a[href*="#/member"]',
                            'a[href*="member"]',
                            '.umb-navigation__link[href*="member"]',
                            '.umb-nav-item[data-element="section-member"]'
                        ];

                        memberSelectors.forEach(selector => {
                            try {
                                const elements = document.querySelectorAll(selector);
                                elements.forEach(element => {
                                    if (element && !element.closest('.umb-user-profile') && !element.closest('.umb-user')) {
                                        element.style.display = '';
                                        element.style.visibility = 'visible';
                                        element.style.opacity = '1';
                                    }
                                });
                            } catch (e) {
                                // Ignore selector errors
                            }
                        });
                    }

                    // Non-admins: proceed to hide member tab
                    function hideMemberTab() {
                        let attempts = 0;
                        const maxAttempts = 20;

                        function tryHide() {
                            // Multiple selectors to find the member tab
                            const selectors = [
                                'li[data-element="section-member"]',
                                'li[data-element="section-members"]',
                                'a[href*="#/member"]',
                                'a[href*="member"]',
                                '.umb-navigation__link[href*="member"]',
                                '.umb-nav-item[data-element="section-member"]',
                                '.umb-navigation li:has(a[href*="member"])'
                            ];

                            let tabFound = false;

                            selectors.forEach(selector => {
                                try {
                                    const tabs = document.querySelectorAll(selector);
                                    tabs.forEach(tab => {
                                        // Make sure we're not touching user profile/logout elements
                                        if (tab &&
                                            tab.style.display !== 'none' &&
                                            !tab.closest('.umb-user-profile') &&
                                            !tab.closest('.umb-user') &&
                                            !tab.closest('[data-element="user"]') &&
                                            !tab.closest('.umb-header') &&
                                            !tab.classList.contains('umb-user-profile') &&
                                            !tab.classList.contains('umb-user')) {

                                            // Additional check: make sure this is actually a navigation item
                                            const isNavItem = tab.closest('.umb-navigation') ||
                                                tab.closest('.umb-nav') ||
                                                tab.getAttribute('data-element') === 'section-member' ||
                                                tab.classList.contains('umb-nav-item');

                                            if (isNavItem) {
                                                // Hide the tab completely
                                                tab.style.display = 'none !important';
                                                tab.style.visibility = 'hidden';
                                                tab.style.opacity = '0';

                                                // Also hide parent li if this is a link
                                                if (tab.tagName === 'A' && tab.closest('li')) {
                                                    const parentLi = tab.closest('li');
                                                    parentLi.style.display = 'none !important';
                                                    parentLi.style.visibility = 'hidden';
                                                }

                                                // Remove from DOM entirely
                                                tab.remove();

                                                tabFound = true;
                                                console.log('Removed Members tab for non-admin user using selector:', selector);
                                            }
                                        }
                                    });
                                } catch (e) {
                                    // Ignore selector errors
                                }
                            });

                            // Additional method: find by text content (more specific)
                            const navItems = document.querySelectorAll('li[data-element*="section"], .umb-nav-item, .umb-navigation li');
                            navItems.forEach(element => {
                                if (element.textContent &&
                                    (element.textContent.toLowerCase().trim() === 'members' ||
                                        element.textContent.toLowerCase().trim() === 'member') &&
                                    (element.getAttribute('data-element') === 'section-member' ||
                                        element.querySelector('a[href*="member"]'))) {
                                    element.remove();
                                    tabFound = true;
                                    console.log('Removed Members tab by specific text content');
                                }
                            });

                            if (!tabFound && attempts < maxAttempts) {
                                attempts++;
                                $timeout(tryHide, 300);
                            }
                        }

                        // Start immediately
                        tryHide();
                    }

                    // Run multiple times with different delays
                    $timeout(hideMemberTab, 100);
                    $timeout(hideMemberTab, 500);
                    $timeout(hideMemberTab, 1000);
                    $timeout(hideMemberTab, 2000);

                    // Enhanced route change monitoring
                    $rootScope.$on('$routeChangeStart', (event, next, current) => {
                        if (next && next.originalPath && next.originalPath.toLowerCase().includes('/member')) {
                            console.warn('Non-admin user tried to navigate to member route. Blocking...');
                            event.preventDefault();
                            $location.path('/content');
                        }
                    });

                    $rootScope.$on('$routeChangeSuccess', () => {
                        preventDirectMemberAccess();
                        $timeout(hideMemberTab, 100);
                        $timeout(hideMemberTab, 500);
                    });

                    $rootScope.$on('$locationChangeStart', (event, newUrl, oldUrl) => {
                        if (newUrl && newUrl.toLowerCase().includes('/member')) {
                            console.warn('Non-admin user tried to change location to member. Blocking...');
                            event.preventDefault();
                            $location.path('/content');
                        }
                    });

                    $rootScope.$on('$locationChangeSuccess', () => {
                        preventDirectMemberAccess();
                        $timeout(hideMemberTab, 100);
                        $timeout(hideMemberTab, 500);
                    });

                    // Additional event listeners for better coverage
                    $rootScope.$on('$stateChangeStart', (event, toState, toParams) => {
                        if (toState && toState.url && toState.url.toLowerCase().includes('/member')) {
                            console.warn('Non-admin user tried to change state to member. Blocking...');
                            event.preventDefault();
                            $location.path('/content');
                        }
                    });

                    $rootScope.$on('$stateChangeSuccess', () => {
                        preventDirectMemberAccess();
                        $timeout(hideMemberTab, 100);
                        $timeout(hideMemberTab, 500);
                    });

                    // Enhanced continuous monitoring for non-admin users
                    const continuousCheck = $interval(() => {
                        const currentUrl = window.location.href.toLowerCase();
                        if (currentUrl.includes('/umbraco#/member') || currentUrl.includes('/umbraco/#/member')) {
                            console.warn('Non-admin user detected in member section during continuous check. Redirecting...');
                            $location.path('/content');
                        }
                        hideMemberTab();
                    }, 500); // Check every 500ms

                    // Clean up after 5 minutes
                    $timeout(() => $interval.cancel(continuousCheck), 300000);

                    // More aggressive periodic check
                    const periodicCheck = $interval(() => {
                        hideMemberTab();
                    }, 2000);
                    $timeout(() => $interval.cancel(periodicCheck), 120000); // Run for 2 minutes

                    // Watch for DOM changes and hide member tab when it appears
                    const observer = new MutationObserver((mutations) => {
                        mutations.forEach((mutation) => {
                            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                                // Only trigger if the added nodes are in navigation area
                                let shouldHide = false;
                                mutation.addedNodes.forEach(node => {
                                    if (node.nodeType === 1) { // Element node
                                        const isNavigation = node.closest && (
                                            node.closest('.umb-navigation') ||
                                            node.closest('.umb-nav') ||
                                            node.getAttribute('data-element') === 'section-member'
                                        );
                                        if (isNavigation) {
                                            shouldHide = true;
                                        }
                                    }
                                });

                                if (shouldHide) {
                                    $timeout(hideMemberTab, 50);
                                }
                            }
                        });
                    });

                    // Start observing
                    observer.observe(document.body, {
                        childList: true,
                        subtree: true
                    });

                    // Stop observing after 5 minutes
                    $timeout(() => observer.disconnect(), 300000);

                }).catch(function (err) {
                    console.error('Error getting user from authResource:', err);
                    // If auth fails, assume non-admin and block member access
                    isAuthCheckComplete = true;
                    isAdminUser = false;

                    const currentUrl = window.location.href.toLowerCase();
                    if (currentUrl.includes('/umbraco#/member') || currentUrl.includes('/umbraco/#/member')) {
                        console.warn('Auth failed and member access attempted. Redirecting...');
                        window.location.href = '/umbraco#/content';
                    }
                });
            }
        ]);
})();`

//this is mymember-section-hide.js
{
  "javascript": [
    "~/App_Plugins/MemberSectionHide/member-section-hide.js"
  ]
}

//its my package.manifest