Skip to content

Commit f76decb

Browse files
committed
Replace authorization config example with shipped config in v7 docs
The example used pre-6.0 AdminRole::ROLE_* constants and invented dot-notation permissions, and was invalid PHP. Documents the real enum-keyed config, the child-to-parent inheritance direction, and how AuthorizationMiddleware resolves a request. Signed-off-by: arhimede <julian@dotkernel.com>
1 parent 8c4b028 commit f76decb

1 file changed

Lines changed: 142 additions & 42 deletions

File tree

‎docs/book/v7/core-features/authorization.md‎

Lines changed: 142 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
## Summary
44

55
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.
78

89
## Details
910

@@ -13,7 +14,7 @@ Authorization is the process by which a system takes a validated identity and ch
1314

1415
## How it works
1516

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.
1718
RBAC comes in to ensure that each entity has the appropriate role and permission to access a resource.
1819

1920
The authorization happens through the `Api\App\Middleware\AuthorizationMiddleware` middleware.
@@ -24,59 +25,144 @@ Dotkernel API makes use of `mezzio-authorization-rbac` and includes the full con
2425

2526
The configuration file for the role and permission definitions is `config/autoload/authorization.global.php`.
2627

28+
Roles are the backed enums `Core\Admin\Enum\AdminRoleEnum` (`superuser`, `admin`) and
29+
`Core\User\Enum\UserRoleEnum` (`user`, `guest`), so the array keys are their `->value` strings.
30+
2731
```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',
32+
use Core\Admin\Enum\AdminRoleEnum;
33+
use Core\User\Enum\UserRoleEnum;
34+
35+
return [
36+
'mezzio-authorization-rbac' => [
37+
'roles' => [
38+
AdminRoleEnum::Superuser->value => [],
39+
AdminRoleEnum::Admin->value => [
40+
AdminRoleEnum::Superuser->value,
41+
],
42+
UserRoleEnum::Guest->value => [
43+
UserRoleEnum::User->value,
44+
],
4945
],
50-
UserRole::ROLE_GUEST => [
51-
'other.routes',
52-
'security.refresh-token',
53-
'error.report',
54-
'home',
46+
'permissions' => [
47+
AdminRoleEnum::Superuser->value => [],
48+
AdminRoleEnum::Admin->value => [
49+
'admin::list-admin',
50+
'admin::create-admin',
51+
'admin::delete-admin',
52+
'admin::view-admin',
53+
'admin::update-admin',
54+
'admin::list-role',
55+
'admin::view-role',
56+
'admin::view-account',
57+
'admin::update-account',
58+
'user::list-user',
59+
'user::create-user',
60+
'user::delete-user',
61+
'user::view-user',
62+
'user::update-user',
63+
'user::delete-user-avatar',
64+
'user::view-user-avatar',
65+
'user::create-user-avatar',
66+
'user::list-role',
67+
'user::view-role',
68+
'user::activate-user',
69+
'user::deactivate-user',
70+
'app::create-error-report',
71+
'app::view-index',
72+
],
73+
UserRoleEnum::User->value => [
74+
'user::delete-account',
75+
'user::view-account',
76+
'user::update-account',
77+
'user::delete-account-avatar',
78+
'user::view-account-avatar',
79+
'user::create-account-avatar',
80+
],
81+
UserRoleEnum::Guest->value => [
82+
'app::create-error-report',
83+
'app::view-index',
84+
'user::activate-account',
85+
'user::request-activate-account',
86+
'user::recover-account',
87+
'user::check-account-reset-password',
88+
'user::update-account-reset-password',
89+
'user::create-account-reset-password',
90+
'user::create-account',
91+
'security::generate-token',
92+
'security::refresh-token',
93+
],
5594
],
5695
],
57-
],
96+
];
5897
```
5998

99+
That is the complete shipped configuration, not an excerpt.
100+
Between them the three populated roles grant **38 permissions covering all 38 routes**: every route
101+
the application declares is reachable by at least one role, and no permission names a route that
102+
does not exist.
103+
Only `app::view-index` and `app::create-error-report` are granted twice, to both `admin` and `guest`.
104+
60105
> See [mezzio-authorization-rbac](https://docs.mezzio.dev/mezzio-authorization-rbac/v1/basic-usage/)
61106
> for more information.
62107
63108
## Usage
64109

65-
Based on the configuration file above, we have two admin roles (`superuser`, `admin`) and two user roles (`user`, `guest`).
110+
Based on the configuration file above, we have two admin roles (`superuser`, `admin`) and two user
111+
roles (`user`, `guest`).
112+
113+
A permission in Dotkernel API is a **route name** — the third argument given to the route in a
114+
module's `RoutesDelegator`. To list the names you can grant, run
115+
`php ./bin/cli.php route:list`; see [Displaying Dotkernel API endpoints](../commands/display-available-endpoints.md).
116+
117+
### How inheritance works here
118+
119+
The array under `roles` maps a role to its **parents**, and inheritance runs in the direction that
120+
often surprises people: a **parent receives the permissions of its children**, because
121+
`laminas-permissions-rbac` resolves `hasPermission()` by walking down into child roles.
122+
123+
So in the shipped configuration:
66124

67-
Roles inherit the permissions from their parents:
125+
| Entry | Meaning |
126+
| --- | --- |
127+
| `superuser => []` | `superuser` has no parent |
128+
| `admin => [superuser]` | `superuser` is the parent of `admin`, so **`superuser` inherits everything granted to `admin`** |
129+
| `guest => [user]` | `user` is the parent of `guest`, so **`user` inherits everything granted to `guest`** |
68130

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
131+
That is why `superuser` needs no permissions of its own: its list is empty, yet it can reach all 23
132+
routes granted to `admin`.
73133

74-
For each role we defined an array of permissions.
75-
A permission in Dotkernel API is basically a route name.
134+
It is also why `user` ends up with 17 effective permissions — its own 6 plus the 11 granted to
135+
`guest` — while `guest` keeps only its own 11 and cannot reach the account routes reserved for a
136+
signed-in user.
76137

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.
138+
Effective totals, once inheritance is applied:
78139

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.
140+
| Role | Own | Inherited | Effective |
141+
| --- | --- | --- | --- |
142+
| `superuser` | 0 | 23 from `admin` | 23 |
143+
| `admin` | 23 | — | 23 |
144+
| `user` | 6 | 11 from `guest` | 17 |
145+
| `guest` | 11 | — | 11 |
146+
147+
### How a request is authorized
148+
149+
`AuthorizationMiddleware` injects `Mezzio\Authorization\AuthorizationInterface` rather than an RBAC
150+
class directly — the RBAC adapter is bound by `Mezzio\Authorization\Rbac\ConfigProvider`, registered
151+
in `config/config.php`.
152+
153+
For each request it:
154+
155+
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`. An unrecognised client is rejected.
156+
2. Rejects an account that is inactive, or a user that has been deleted.
157+
3. Replaces the identity's roles with the role names read from that record.
158+
4. Calls `isGranted()` once per role and allows the request as soon as **any** role grants the route.
159+
160+
If no role grants it, the response is `403 Forbidden` with
161+
`You are not allowed to access this resource.`
162+
163+
> Note this middleware returns a plain JSON error body rather than a Problem Details document, so an
164+
> authorization failure does not look like the errors described in
165+
> [Problem details](../extended-features/problem-details.md).
80166
81167
## FAQ
82168

@@ -98,15 +184,18 @@ A route with no permission entry is unreachable for that role.
98184
**Q: Which access control model is used?**
99185

100186
A: RBAC, via `mezzio-authorization-rbac` backed by `laminas-permissions-rbac`.
187+
`AuthorizationMiddleware` depends only on `Mezzio\Authorization\AuthorizationInterface`, so the adapter is selected by configuration rather than hardcoded.
101188

102189
**Q: How does role inheritance work here?**
103190

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.
191+
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.
192+
Because `admin` lists `superuser`, `superuser` receives everything granted to `admin`, which is why `superuser` needs no permissions of its own.
193+
Likewise `guest` lists `user`, so `user` inherits the guest permissions on top of its own.
106194

107195
**Q: Where are roles stored?**
108196

109-
A: Each authenticatable entity — admin or user — has its own `roles` table where its roles are defined.
197+
A: In `admin_role` and `user_role`, with `admin_roles` and `user_roles` as the join tables that assign them to accounts.
198+
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.
110199

111200
**Q: Which middleware enforces this?**
112201

@@ -116,3 +205,14 @@ See [Middleware flow](../flow/middleware-flow.md).
116205
**Q: Can I use ACL instead of RBAC?**
117206

118207
A: The ACL adapter ships with the project, but RBAC is what Dotkernel API is configured for; switching means replacing the authorization configuration.
208+
Because the middleware only knows `AuthorizationInterface`, no application code needs to change.
209+
210+
**Q: Do the permissions cover every route?**
211+
212+
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.
213+
`app::view-index` and `app::create-error-report` are the only two granted to two roles.
214+
215+
**Q: What does a rejected request look like?**
216+
217+
A: `403 Forbidden` with `You are not allowed to access this resource.`
218+
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

Comments
 (0)