Security and authorization
Arrel decides access on the server, at every endpoint. The frontend hides the buttons you cannot use, but that is a convenience: the real protection is that the API refuses them.
Layers of a request
A request to a Resource's API goes through, in this order:
- The middleware of
config('arrel.middleware')(for example, identifying the tenant). - The Sanctum session (
auth:sanctum). - Email verification and the second factor, if they are enabled (see Authentication).
- The middleware of
config('arrel.authenticated_middleware'). - The authorization of the Resource, the page or the action.
Resources
The five methods canViewAny(), canView(), canCreate(), canEdit() and canDelete() delegate to the model's Policy, or can be overridden (see Resources).
A Resource for which canViewAny() is false is closed at every endpoint: the listing, but also the schema, creation, editing, deletion, actions, field options and relations answer 403. Without that rule, a model with no Policy would leave canCreate() or canEdit() open independently.
This means that, for a model without a Policy, everything is allowed for any authenticated user. If the Resource must be restricted, define a Policy or override canViewAny().
Actions
- An action with
authorize()uses that closure, with the same arguments ashandle(). - An action without
authorize()requirescanEdit()on the record, orcanCreate()if it isstandalone(). Declareauthorize()on actions that must stay open to more users. - The built-in actions follow their own ability:
ViewActionasks forcanView(),EditActionandRestoreActionask forcanEdit(),DeleteActionandForceDeleteActionask forcanDelete(). - The options an action offers for its relation fields are only served to users who can run it.
Relations
If a Relation field declares query(), the scope is also applied on save: a record that the scope hides from the dropdown is refused with a validation error, even if someone sends its identifier by hand. It works the same in the forms of a Resource, of a Relation manager and of an action.
Relation::make('author_id')
->related(User::class)
->query(fn (Builder $query): Builder => $query->where('team_id', auth()->user()->team_id))In the query() closure of a field in a Relation manager or an action, the parent record arrives as the third argument.
File uploads
- Uploads are limited to 60 per minute and user.
- When saving a record, only files that were really uploaded to the temporary directory (
arrel-temp) are moved. A value that does not match a real upload is not moved. - The content of
RichEditorfields is sanitized on the server on every save.
Attempt limits
| What | Limit |
|---|---|
| Sign-in | 5 failed attempts per email and IP address |
| Second factor of a pending sign-in | 5 wrong codes, and it expires after 5 minutes |
| Sending the verification email | 3 per minute |
| Confirming or turning off the second factor | 10 per minute |
| File uploads | 60 per minute and user |
Pages
Page::canView() decides whether a page is visible and accessible. A page that cannot be viewed leaves the navigation and answers 403 on the API. For a CustomPage, visible() controls the navigation entry; the authorization of the data the page loads must be done at your application's endpoints, which are your code.
Checklist
- Every exposed model has a Policy, or the Resource overrides the
can...()methods. - Actions that must not be only for editors declare
authorize(). Relationfields that show other people's records havequery().sanctum.statefulcontains only your panel's domains.- The second factor is mandatory (
isRequiredFor()) for the roles with the most privileges.