Milk Admin

Access Control and Permissions

Revision: 2026/01/28

Control access to modules and actions using built-in access levels and advanced permission systems.


Basic Access Levels

MilkAdmin provides four built-in access levels that can be applied to modules and controller methods:

Level Description
public Anyone can access (no authentication required)
registered Only logged-in users can access
authorized Requires specific permission verification
admin Only administrators can access

Module-Level Access

Set the default access level for an entire module in the configure() method:

class PostsModule extends AbstractModule
{
    protected function configure($rule): void
    {
        $rule->page('posts')
             ->title('Posts')
             ->menu('Posts', '', 'bi bi-file-earmark-post-fill', 10)
             ->access('registered');  // Module requires login
    }
}

The menu item will only appear to users who meet the access level requirement.


Method-Level Access Override

Override the module's access level for specific methods using the #[AccessLevel] attribute:

#[RequestAction('home')]
public function postsList() {
    // Inherits module's 'registered' access level
    $response = ['page' => $this->page, 'title' => $this->title];
    $response['html'] = TableBuilder::create($this->model, 'idTablePosts')
        ->asLink('title', '?page='.$this->page.'&action=edit&id=%id%')
        ->setDefaultActions()
        ->render();
    Response::render(__DIR__ . '/Views/list_page.php', $response);
}

#[AccessLevel('authorized')]
#[RequestAction('edit')]
public function postEdit() {
    // Requires specific permission verification
    $response = ['page' => $this->page, 'title' => $this->title];
    $response['form'] = FormBuilder::create($this->model, $this->page)
        ->getForm();
    Response::render(__DIR__ . '/Views/edit_page.php', $response);
}

Advanced Permissions

Define granular permissions within modules to control specific actions. Permissions can be managed through the Auth module's user interface.

Defining Permissions

Use the permissions() method in module configuration:

protected function configure($rule): void
{
    $rule->page('posts')
         ->access('registered')
         ->permissions([
             'access' => 'Access Posts Module',
             'delete' => 'Delete Posts'
         ]);
}
The first permission (access) is automatically verified when using access('authorized').

Permission Verification

Use Permissions::check() to verify specific permissions in controller methods:

#[RequestAction('delete')]
public function deletePost() {
    if (!Permissions::check('posts.delete')) {
        $queryString = Route::getQueryString();
        Route::redirect('?page=deny&redirect='.Route::urlsafeB64Encode($queryString));
    }

    // Permission granted, proceed with deletion
    // ...
}

Permission format: 'module_page.permission_name'

You can also share a single permission across multiple modules by defining a permission key in group.permission format. If the key contains exactly one dot, the part before the dot is used as the permission group and the part after the dot is the permission key. This makes the Users module show a single permission entry that grants access to all modules using the same group/key.

$rule->access('authorized')
     ->permissions(['school.access' => 'Access School']);

Important: User Initialization Before Permission Checks

Critical: If you attempt to verify permissions before the user has been loaded, the system will treat the user as an administrator by default.

In modules, inside the configure() method, before performing any checkPermission or Permissions::check() call, you must ensure that the current user has been initialized.

How to Initialize the User

You can initialize the user by simply requesting the Auth contract:

protected function configure($rule): void
{
    // Initialize the authenticated user
    Get::make('Auth');

    // Now it's safe to check permissions
    if (!Permissions::check('posts.access')) {
        // Permission denied
    }

    $rule->page('posts')
         ->title('Posts')
         ->access('authorized')
         ->permissions([
             'access' => 'Access Posts Module',
             'delete' => 'Delete Posts'
         ]);
}

Calling Get::make('Auth') automatically initializes the permissions for the logged-in user. Without this initialization, the permission system cannot determine the actual user's permissions and will default to administrator privileges.

Note: This initialization is automatically handled in controller methods after the modules_loaded hook. However, in the configure() method or early module initialization, you must explicitly call Get::make('Auth') before checking permissions.

Permission System Behavior

When access('authorized') is Used

Setting a method's access level to authorized automatically checks the first permission defined in the module's permissions() array. If that first permission uses the group.permission format, the check uses the provided group/key instead of the module page.

// In module configuration
->permissions([
    'access' => 'Access Posts Module',  // This permission is checked
    'delete' => 'Delete Posts'
])

// In controller
#[AccessLevel('authorized')]
#[RequestAction('edit')]
public function postEdit() {
// Automatically verifies 'posts.access' permission
}

Manual Permission Checks

For additional permissions, use explicit Permissions::check() calls:

if (!Permissions::check('posts.delete')) {
    // User lacks 'delete' permission
    // Redirect or show error
}

Complete Example

class PostsModule extends AbstractModule
{
    protected function configure($rule): void
    {
        $rule->page('posts')
             ->title('Posts')
             ->access('registered')
             ->permissions([
                 'access' => 'Access Posts Module',
                 'create' => 'Create Posts',
                 'edit' => 'Edit Posts',
                 'delete' => 'Delete Posts'
             ]);
    }
}

class PostsController extends AbstractController
{
    #[RequestAction('home')]
    public function postsList() {
        // Inherits 'registered' access level
    }

    #[AccessLevel('authorized')]
    #[RequestAction('edit')]
    public function postEdit() {
        // Automatically checks 'posts.access' permission

        if (!Permissions::check('posts.edit')) {
            Route::redirect('?page=deny');
        }

        // User has both 'access' and 'edit' permissions
    }

    #[AccessLevel('authorized')]
    #[RequestAction('delete')]
    public function postDelete() {
        if (!Permissions::check('posts.delete')) {
            Route::redirect('?page=deny');
        }

        // User has 'delete' permission
    }
}

See Also

Loading...