How to prevent access to wp-admin for certain user roles?

I did try using the plugin Front End Users but this clashes with something as it prevents access to some front end pages. So I need to manually just set it so that anyone who isn’t one of two usernames (or roles) can’t access wp-admin.

Related posts

Leave a Reply

6 comments

  1. Plugin

    It’s basically just a user capability check, followed by a redirect in an exit call. It then redirects to the site the request came from.

    <?php
    ! defined( 'ABSPATH' ) AND exit;
    /* Plugin Name: (#66093) »kaiser« Deny Admin-UI access for certain roles */
    
    
    function wpse66093_no_admin_access()
    {
        // Do not run if the user is logged in and trying to log out
        // This might need one or two more checks.
        // Especially if you have custom login/logout/reset password/etc rules and routes set up.
        if ( 
            ! is_admin()
            || (
                is_user_logged_in()
                && isset( $GLOBALS['pagenow'] ) AND 'wp-login.php' === $GLOBALS['pagenow']
            )
        ) {
            return;
        }
    
        $redirect = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : home_url( '/' );
        if ( 
            current_user_can( 'CAPABILITY_NAME_HERE' )
            OR current_user_can( 'CAPABILITY_NAME_HERE' )
        )
            exit( wp_redirect( $redirect ) );
    }
    add_action( 'admin_init', 'wpse66093_no_admin_access', 100 );
    

    Keep in mind that should only work with defaults (see comments in code). Custom login, logout, register, password reset logic might breaks this.

    Roles vs. Capabilities: As role names can change and as roles are just groups of capabilities, it’s best to check against a capability, not a role name. You can find a list of built in roles and capabilities here. Just look at what the most restrictive access is and search for a matching capability. Then assign it above. That’s easier to maintain, in case a role name changes. Yes, you can use a role name as well, which will work in WordPress, but it’s a concept that will bring along a hard to track down bug when a role name changes.

    Note: Do not think about roles in a hierarchical manner. Think about the accountant whose email address you enter in some SaaS backend to receive the invoice. Most developers will not have access to billing details and neither will the accountant have access to deployment settings or security credentials. They have differently named roles with equally “high” capabilities, but for completely different parts. Keep this example in mind, when you write capability checks or add custom capabilities to a system.

  2. Just revisited that answer as it wasn’t updated in a long time. Year is 2021.

    The accepted answer is checking whether the current page is a wp-login.php page OR an admin page WHILE using an admin_init hook, this is nonsense.

    admin_init fires as an admin screen or script is being initialized. It does NOT just run on user-facing admin screens. It runs on admin-ajax.php and admin-post.php as well.

    In no cases whatsoever will it fire on a wp-login.php as it is NOT an admin screen. Tho it will indeed fire upon an ajax request, therefore that case should be handle. wp_doing_ajax() determines whether the current request is a WordPress Ajax request.

    In the following example I’m using the delete_posts user capability to allow admin, editor and author access to the WordPress backend. Refer to the Capability vs. Role Table for a more restrictive approach.

    As a reminder here are the default WordPress roles (Summary of Roles):

    super admin → admin → editor → author → contributor → subscriber.

    With a single site WordPress installation, Administrators are, in effect, Super Admins.

    I’ve chosen to use wp_die() instead of blindly redirecting users. wp_die() offer some kind of user onboarding as it kills WordPress execution and displays an HTML page with an error message. The same approach could be done with redirecting users to a 404 page. Anything that explain the situation is better than a blind home page redirect.

    add_action( 'admin_init', 'restrict_wpadmin_access' );
    if ( ! function_exists( 'restrict_wpadmin_access' ) ) {
        function restrict_wpadmin_access() {
            if ( wp_doing_ajax() || current_user_can( 'delete_posts' ) ) {
                return;
            } else {
                header( 'Refresh: 2; ' . esc_url( home_url() ) );
                $args = array(
                    'back_link' => true,
                );
                wp_die( 'Restricted access.', 'Error', $args );
            };
        };
    };
    

    To prevent the default redirection to the wp-admin.php upon login I’m using the login_redirect hook filter which Filters the login redirect URL. I’m redirecting them to their own profile page using get_author_posts_url(), but you can easily redirect to whatever page you would like. You could also conditionally redirect based on the user role (eg: admin to the admin page, rest to profile), everything is explained on the CODEX page example section.

    add_filter( 'login_redirect', 'redirect_user_to_profile_on_login', 10, 3 );
    if ( ! function_exists( 'redirect_user_to_profile_on_login' ) ) {
        function redirect_user_to_profile_on_login( $redirect_to, $requested_redirect_to, $user ) {
            if ( $user && is_object( $user ) && is_a( $user, 'WP_User' ) ) {
                $redirect_to = esc_url( get_author_posts_url( $user->ID ) );
            };
            return $redirect_to;
        };
    };
    
  3. The accepted answer mentions User Role but actually uses the function for User Capability

    Here’s the solution for User Roles

     function wpse66094_no_admin_access() {
        $redirect = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : home_url( '/' );
        global $current_user;
        $user_roles = $current_user->roles;
        $user_role = array_shift($user_roles);
        if($user_role === 'YOUR_USER_ROLE_HERE'){
            exit( wp_redirect( $redirect ) );
        }
     }
    
    add_action( 'admin_init', 'wpse66094_no_admin_access', 100 );
    
  4. Based on the answer provided by @kaiser (thank you btw), this is my working code, just in any case someone needs it. It is placed in functions.php file.

    The condition used is, if the user can’t manage_options or edit_posts.

    function wpse66093_no_admin_access() {
        $redirect = home_url( '/' );
        if ( ! ( current_user_can( 'manage_options' ) || current_user_can( 'edit_posts' ) ) )
            exit( wp_redirect( $redirect ) );
    }
    add_action( 'admin_init', 'wpse66093_no_admin_access', 100 );
    
  5. With @kaiser’s answer I found you’ll need to utilize admin_menu hook over admin_init as it fires before the !user_can_access_admin_page() check in wp-admin/includes/menu.php otherwise if the user doesn’t have ‘read’ access to the dashboard they’ll just get the ‘You do not have sufficient permissions to access this page.’ page rather than being redirected.