You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 275e549
Browse filesBrowse the repository at this point in the historyBrowse files
Authorization decides whether an already-authenticated identity may reach a given resource.
6
-
Dotkernel API implements it with role-based access control through `Mezzio\Authorization\Rbac\LaminasRbac`, applied by `AuthorizationMiddleware` and configured in `config/autoload/authorization.global.php`, where each permission is a route name and roles inherit from their parents.
6
+
Dotkernel API implements it with role-based access control through `Mezzio\Authorization\Rbac\LaminasRbac`, applied by `AuthorizationMiddleware` and configured in `config/autoload/authorization.global.php`, where each permission is a route name.
7
+
Inheritance runs from child to parent, so `superuser` inherits every permission granted to `admin` without declaring any of its own.
7
8
8
9
## Details
9
10
@@ -13,7 +14,7 @@ Authorization is the process by which a system takes a validated identity and ch
13
14
14
15
## How it works
15
16
16
-
In Dotkernel API each authenticatable entity (admin/user) comes with their `roles`table where you can define roles for each entity.
17
+
In Dotkernel API each authenticatable entity (admin/user) has its own role table — `admin_role` and `user_role` — plus a join table, `admin_roles` and `user_roles`, assigning roles to accounts.
17
18
RBAC comes in to ensure that each entity has the appropriate role and permission to access a resource.
18
19
19
20
The authorization happens through the `Api\App\Middleware\AuthorizationMiddleware` middleware.
@@ -24,59 +25,129 @@ Dotkernel API makes use of `mezzio-authorization-rbac` and includes the full con
24
25
25
26
The configuration file for the role and permission definitions is `config/autoload/authorization.global.php`.
26
27
28
+
Roles are the backed enums `Core\Admin\Enum\AdminRoleEnum` (`superuser`, `admin`) and `Core\User\Enum\UserRoleEnum` (`user`, `guest`), so the array keys are their `->value` strings.
29
+
27
30
```php
28
-
'mezzio-authorization-rbac' => [
29
-
'roles' => [
30
-
AdminRole::ROLE_SUPERUSER => [],
31
-
AdminRole::ROLE_ADMIN => [
32
-
AdminRole::ROLE_SUPERUSER,
33
-
],
34
-
UserRole::ROLE_GUEST => [
35
-
UserRole::ROLE_USER,
36
-
],
37
-
],
38
-
'permissions' => [
39
-
AdminRole::ROLE_SUPERUSER => [],
40
-
AdminRole::ROLE_ADMIN => [
41
-
'other.routes'
42
-
'admin.list',
43
-
'home'
44
-
],
45
-
UserRole::ROLE_USER => [
46
-
'other.routes',
47
-
'user.my-account.update',
48
-
'user.my-account.view',
31
+
use Core\Admin\Enum\AdminRoleEnum;
32
+
use Core\User\Enum\UserRoleEnum;
33
+
34
+
return [
35
+
'mezzio-authorization-rbac' => [
36
+
'roles' => [
37
+
AdminRoleEnum::Superuser->value => [],
38
+
AdminRoleEnum::Admin->value => [
39
+
AdminRoleEnum::Superuser->value,
40
+
],
41
+
UserRoleEnum::Guest->value => [
42
+
UserRoleEnum::User->value,
43
+
],
49
44
],
50
-
UserRole::ROLE_GUEST => [
51
-
'other.routes',
52
-
'security.refresh-token',
53
-
'error.report',
54
-
'home',
45
+
'permissions' => [
46
+
AdminRoleEnum::Superuser->value => [],
47
+
AdminRoleEnum::Admin->value => [
48
+
'admin::list-admin',
49
+
'admin::create-admin',
50
+
'admin::delete-admin',
51
+
'admin::view-admin',
52
+
'admin::update-admin',
53
+
'admin::list-role',
54
+
'admin::view-role',
55
+
'admin::view-account',
56
+
'admin::update-account',
57
+
'user::list-user',
58
+
'user::create-user',
59
+
'user::delete-user',
60
+
'user::view-user',
61
+
'user::update-user',
62
+
'user::delete-user-avatar',
63
+
'user::view-user-avatar',
64
+
'user::create-user-avatar',
65
+
'user::list-role',
66
+
'user::view-role',
67
+
'user::activate-user',
68
+
'user::deactivate-user',
69
+
'app::create-error-report',
70
+
'app::view-index',
71
+
],
72
+
UserRoleEnum::User->value => [
73
+
'user::delete-account',
74
+
'user::view-account',
75
+
'user::update-account',
76
+
'user::delete-account-avatar',
77
+
'user::view-account-avatar',
78
+
'user::create-account-avatar',
79
+
],
80
+
UserRoleEnum::Guest->value => [
81
+
'app::create-error-report',
82
+
'app::view-index',
83
+
'user::activate-account',
84
+
'user::request-activate-account',
85
+
'user::recover-account',
86
+
'user::check-account-reset-password',
87
+
'user::update-account-reset-password',
88
+
'user::create-account-reset-password',
89
+
'user::create-account',
90
+
'security::generate-token',
91
+
'security::refresh-token',
92
+
],
55
93
],
56
94
],
57
-
],
95
+
];
58
96
```
59
97
60
-
> See [mezzio-authorization-rbac](https://docs.mezzio.dev/mezzio-authorization-rbac/v1/basic-usage/)
61
-
> for more information.
98
+
That is the complete shipped configuration, not an excerpt.
99
+
Between them the three populated roles grant **38 permissions covering all 38 routes**: every route the application declares is reachable by at least one role, and no permission names a route that does not exist.
100
+
Only `app::view-index` and `app::create-error-report` are granted twice, to both `admin` and `guest`.
101
+
102
+
> See [mezzio-authorization-rbac](https://docs.mezzio.dev/mezzio-authorization-rbac/v1/basic-usage/) for more information.
62
103
63
104
## Usage
64
105
65
106
Based on the configuration file above, we have two admin roles (`superuser`, `admin`) and two user roles (`user`, `guest`).
66
107
67
-
Roles inherit the permissions from their parents:
108
+
A permission in Dotkernel API is a **route name** — the third argument given to the route in a module's `RoutesDelegator`.
109
+
To list the names you can grant, run `php ./bin/cli.php route:list`; see [Displaying Dotkernel API endpoints](../commands/display-available-endpoints.md).
110
+
111
+
### How inheritance works here
112
+
113
+
The array under `roles` maps a role to its **parents**, and inheritance runs in the direction that often surprises people: a **parent receives the permissions of its children**, because `laminas-permissions-rbac` resolves `hasPermission()` by walking down into child roles.
114
+
115
+
So in the shipped configuration:
116
+
117
+
| Entry | Meaning |
118
+
| --- | --- |
119
+
|`superuser => []`|`superuser` has no parent |
120
+
|`admin => [superuser]`|`superuser` is the parent of `admin`, so **`superuser` inherits everything granted to `admin`**|
121
+
|`guest => [user]`|`user` is the parent of `guest`, so **`user` inherits everything granted to `guest`**|
68
122
69
-
-`superuser` has no parent
70
-
-`admin` has `superuser` as a parent which means `superuser` also has `admin` permissions
71
-
-`user` has no parent
72
-
-`guest` has `user` as a parent which means `user` also has `guest` permissions
123
+
That is why `superuser` needs no permissions of its own: its list is empty, yet it can reach all 23 routes granted to `admin`.
73
124
74
-
For each role we defined an array of permissions.
75
-
A permission in Dotkernel API is basically a route name.
125
+
It is also why `user` ends up with 17 effective permissions — its own 6 plus the 11 granted to `guest` — while `guest` keeps only its own 11 and cannot reach the account routes reserved for a signed-in user.
76
126
77
-
As you can see, the `superuser` does not have its own permissions, because it gains all the permissions from `admin`, no need to define explicit permissions.
127
+
Effective totals, once inheritance is applied:
78
128
79
-
The `user` role, gains all the permission from `guest` so no need to define that `user` can access `home` route, but `guest` cannot access user-specific routes.
129
+
| Role | Own | Inherited | Effective |
130
+
| --- | --- | --- | --- |
131
+
|`superuser`| 0 | 23 from `admin`| 23 |
132
+
|`admin`| 23 | — | 23 |
133
+
|`user`| 6 | 11 from `guest`| 17 |
134
+
|`guest`| 11 | — | 11 |
135
+
136
+
### How a request is authorized
137
+
138
+
`AuthorizationMiddleware` injects `Mezzio\Authorization\AuthorizationInterface` rather than an RBAC class directly — the RBAC adapter is bound by `Mezzio\Authorization\Rbac\ConfigProvider`, registered in `config/config.php`.
139
+
140
+
For each request it:
141
+
142
+
1. Reads `oauth_client_id` from the authenticated identity and loads the matching record — `admin` from the `admin` table, `frontend` from the `user` table, or a `Guest` instance when the client is `guest`.
143
+
An unrecognised client is rejected.
144
+
2. Rejects an account that is inactive, or a user that has been deleted.
145
+
3. Replaces the identity's roles with the role names read from that record.
146
+
4. Calls `isGranted()` once per role and allows the request as soon as **any** role grants the route.
147
+
148
+
If no role grants it, the response is `403 Forbidden` with `You are not allowed to access this resource.`
149
+
150
+
> Note this middleware returns a plain JSON error body rather than a Problem Details document, so an authorization failure does not look like the errors described in [Problem details](../extended-features/problem-details.md).
80
151
81
152
## FAQ
82
153
@@ -98,15 +169,18 @@ A route with no permission entry is unreachable for that role.
98
169
**Q: Which access control model is used?**
99
170
100
171
A: RBAC, via `mezzio-authorization-rbac` backed by `laminas-permissions-rbac`.
172
+
`AuthorizationMiddleware` depends only on `Mezzio\Authorization\AuthorizationInterface`, so the adapter is selected by configuration rather than hardcoded.
101
173
102
174
**Q: How does role inheritance work here?**
103
175
104
-
A: A role listed inside another role's entry is its parent's beneficiary: because `admin` lists `superuser`, `superuser` receives everything granted to `admin`.
105
-
That is why `superuser` needs no explicit permissions of its own.
176
+
A: The values listed against a role are its parents, and a parent inherits from its children — `laminas-permissions-rbac` resolves a permission by walking down into child roles.
177
+
Because `admin` lists `superuser`, `superuser` receives everything granted to `admin`, which is why `superuser` needs no permissions of its own.
178
+
Likewise `guest` lists `user`, so `user` inherits the guest permissions on top of its own.
106
179
107
180
**Q: Where are roles stored?**
108
181
109
-
A: Each authenticatable entity — admin or user — has its own `roles` table where its roles are defined.
182
+
A: In `admin_role` and `user_role`, with `admin_roles` and `user_roles` as the join tables that assign them to accounts.
183
+
The role names themselves come from the `AdminRoleEnum` and `UserRoleEnum` backed enums, so adding a role means adding an enum case as well as a row.
110
184
111
185
**Q: Which middleware enforces this?**
112
186
@@ -116,3 +190,14 @@ See [Middleware flow](../flow/middleware-flow.md).
116
190
**Q: Can I use ACL instead of RBAC?**
117
191
118
192
A: The ACL adapter ships with the project, but RBAC is what Dotkernel API is configured for; switching means replacing the authorization configuration.
193
+
Because the middleware only knows `AuthorizationInterface`, no application code needs to change.
194
+
195
+
**Q: Do the permissions cover every route?**
196
+
197
+
A: Yes, exactly. The three populated roles grant 38 permissions across the 38 declared routes, with no route ungranted and no permission naming a route that does not exist.
198
+
`app::view-index` and `app::create-error-report` are the only two granted to two roles.
199
+
200
+
**Q: What does a rejected request look like?**
201
+
202
+
A: `403 Forbidden` with `You are not allowed to access this resource.`
203
+
The same status is returned when the account is inactive, the user was deleted, or the OAuth client is unrecognised, each with its own message.
0 commit comments