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'
]);
}
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
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.
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
- Permissions Class Documentation - Complete permission system reference
- Creating Modules (Posts Example) - Module basics