In Laravel, how does Gate::before let an admin bypass every authorization check, and why must it return null for everyone else?
answer
- runs ahead of every gate and policy
- non-null result is final
- false denies everything
- policy before() needs a matching method
- typed User parameter skips guests
basics
~20 sGate::before callbacks run before every gate and policy; any non-null return becomes the final answer. Return true for admins and null otherwise, because returning false, or a bool like $user->isAdmin(), denies every check for non-admins.
solid answer
~40 s`Gate::before(fn (User $user, string $ability) => ...)` is called ahead of every check. If it returns anything other than `null`, that value is the result and the gate or policy never runs. So the admin bypass is `if ($user->role === 'admin') { return true; }` with no return for others. A common bug is `return $user->isAdmin();`: for everyone else that returns `false`, which denies every ability in the application. A policy can have its own `before($user, $ability)` with the same rules, scoped to that class, but it is only called when the policy has a method for the ability being checked. Guests: a before callback whose first parameter is a non-nullable `User` is skipped when nobody is logged in. `Gate::after` runs afterwards and can only supply a result when the check returned `null`.
code
php · 17 lines<?php
use App\Models\User;
use Illuminate\Support\Facades\Gate;
// AppServiceProvider::boot()
Gate::before(function (User $user, string $ability) {
if ($user->suspended_at !== null) {
return false; // deliberate: suspended accounts can do nothing
}
if ($user->role === 'admin') {
return true;
}
return null; // everyone else: let gates and policies decide
});go deeper
Remember that Gate::before runs first and that returning true grants the ability outright.
Explain why a non-null return is final, why returning a plain boolean breaks non-admins, and when a policy before() is skipped.
Use before for admin bypasses and kill switches deliberately, and audit that no filter returns false by accident.
Decide whether a global superuser bypass is acceptable at all, or whether admin powers should be explicit abilities that can be audited.
## The order of evaluation For every check, the `Gate` service runs, in order: 1. every **global** `Gate::before` callback, until one returns a non-null value; 2. if none did, the **policy** callback — the policy's `before()` then the ability method — or the **gate** registered under the ability name; 3. every `Gate::after` callback, which may fill in a result only if it is still `null`; 4. a `GateEvaluated` event. A final `null` is a **denial**. ## The admin bypass On a publishing platform, admins should be able to edit, publish or delete anything: ```php Gate::before(function (User $user, string $ability) { if ($user->role === 'admin') { return true; } }); ``` - For admins the callback returns `true` and no policy method runs. - For editors and reviewers it returns nothing (`null`), and the normal rules decide. ## The classic bug ```php Gate::before(fn (User $user) => $user->role === 'admin'); // wrong ``` For a non-admin this returns `false`, a **non-null** result, so every check in the application is denied, including an editor updating their own article. The fix is to return `true` or `null`, never a plain boolean. The same trap exists in a policy's `before()`. Returning `false` on purpose is legitimate: a before callback that denies every ability to suspended accounts is a valid kill switch. ## Policy-level `before()` ```php public function before(User $user, string $ability): bool|null { return $user->role === 'managing-editor' ? true : null; } ``` - Scoped to the one policy, so a managing editor bypasses only `ArticlePolicy` checks. - It is only called when the policy **has a method matching the ability**. `Gate::allows('feature', $article)` on a policy with no `feature()` method never reaches `before()`; the Gate falls through to a gate named `feature`, or denies. ## Guests The Gate inspects each callback's first parameter. If there is no logged-in user and that parameter is a non-nullable type (`User $user`), the callback is **skipped**. A global `before` typed `User $user` therefore never runs for guests, and a policy method typed the same way yields `null`, which denies. Use `?User $user` to let a check run for guests, for example to allow reading published articles anonymously. ## `Gate::after` `after` callbacks receive `($user, $ability, $result, $arguments)`. Their return value is used only when `$result` is `null`, so they cannot overturn a decision that a gate or policy made. They are mostly used for audit logging. ## Checklist | Want | Write | |---|---| | Admins may do everything | `before` returns `true` for admins, `null` otherwise | | Suspended users may do nothing | `before` returns `false` for suspended users, `null` otherwise | | Bypass only one model's rules | policy `before()` | | Log every decision | `Gate::after` returning nothing | ## Why a bypass beats sprinkling admin checks Without a `before` filter, every policy method would need `|| $user->role === 'admin'`. That is repetitive and easy to forget in one method, which silently locks admins out of one action or, worse, invites copy-paste mistakes in the other direction. A single `Gate::before` keeps the rule in one place. The trade-off is visibility: a reader of `ArticlePolicy::delete()` cannot see that admins bypass it. Mitigations: 1. keep the global filter tiny and documented in `AppServiceProvider`; 2. prefer a policy-level `before()` when the bypass applies to one model only; 3. log bypass decisions with `Gate::after` if auditors need to know when an admin acted outside the normal rules. ## Testing the filter A useful pair of tests: an editor updating their own article is allowed (proving `before` stays neutral), and an admin deleting someone else's article is allowed (proving the bypass). The first one catches the "returned a plain boolean" bug immediately.
- A policy's before() returns true for managing editors, yet Gate::allows('feature', $article) is false for them. Why?The policy has no `feature()` method. The Gate only builds the policy callback, which is what calls `before()`, when the policy has a method for the ability. With no method it falls back to a gate named `feature`, and with none of those the result is null and denied.
- Can a Gate::after callback turn a policy's false into true?No. An after callback's return value is used only when the result so far is `null`. If a gate or policy returned `false`, that result stands; after callbacks are for filling gaps and for logging.
saying these in an interview costs you the question
- Gate::before only runs when the ability is undefined
- Returning false from before just skips the admin shortcut
- A policy's before() runs even without a matching ability method
- Gate::after can overrule a policy that returned false
- A before callback typed User $user also runs for guests