In Laravel, how do you register a middleware alias such as role and pass it parameters like role:admin,agent from a route?
answer
- alias() takes a name-to-class array
- colon before the parameters
- commas between parameters
- extra string arguments after $next
- a second alias() call replaces the first
basics
~10 sRegister the alias in bootstrap/app.php with $middleware->alias(['role' => EnsureUserHasRole::class]), then write ->middleware('role:admin,agent') on the route. Laravel splits after the colon on commas and passes each value as a string argument after $next.
solid answer
~40 sIn `withMiddleware()` you call `$middleware->alias(['role' => EnsureUserHasRole::class])`; routes can then say `->middleware('role:admin,agent')`. The name resolver splits the string on the first `:`, maps `role` to the class and keeps `admin,agent`; the pipeline then splits that part on commas and calls `handle($request, $next, 'admin', 'agent')`, so the method declares `string ...$roles` after `$next`. Parameters are always strings, are not trimmed, and work without an alias too: `EnsureUserHasRole::class.':admin'`. One trap: `alias()` sets the whole custom alias map each time it is called, so a second call replaces the first; pass every alias in one array. Custom aliases are merged over the defaults such as `auth`, `can`, `signed` and `throttle`, so reusing one of those names overrides the built-in.
code
php · 17 lines<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserHasRole
{
public function handle(Request $request, Closure $next, string ...$roles): Response
{
abort_unless(in_array($request->user()?->role, $roles, true), 403);
return $next($request);
}
}go deeper
Recall alias() in bootstrap/app.php and the route syntax name:param1,param2.
Explain how the resolver and the pipeline split the string, and that each value arrives as a separate string argument after $next.
Point out the alias() overwrite, the override of default aliases, and untrimmed or string-typed parameters as sources of silent authorization bugs.
Decide which checks are parameterised middleware and which belong in policies, so role strings do not scatter across hundreds of route definitions.
## What a middleware alias is A **middleware alias** is a short string that stands for a middleware class. Routes can reference `'role'` instead of `\App\Http\Middleware\EnsureUserHasRole::class`, which keeps route files readable and, more importantly, lets a route pass **parameters** in the same string. Laravel 13 ships default aliases for its own middleware: | Alias | Class | |---|---| | `auth` | `Illuminate\Auth\Middleware\Authenticate` | | `auth.basic` | `Illuminate\Auth\Middleware\AuthenticateWithBasicAuth` | | `auth.session` | `Illuminate\Session\Middleware\AuthenticateSession` | | `cache.headers` | `Illuminate\Http\Middleware\SetCacheHeaders` | | `can` | `Illuminate\Auth\Middleware\Authorize` | | `guest` | `Illuminate\Auth\Middleware\RedirectIfAuthenticated` | | `password.confirm` | `Illuminate\Auth\Middleware\RequirePassword` | | `precognitive` | `Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests` | | `signed` | `Illuminate\Routing\Middleware\ValidateSignature` | | `throttle` | `ThrottleRequests`, or the Redis variant | | `verified` | `Illuminate\Auth\Middleware\EnsureEmailIsVerified` | When Laravel Spark is installed, a `subscribed` alias is added as well. ## Registering your own aliases Custom aliases go in `bootstrap/app.php`: ```php ->withMiddleware(function (Middleware $middleware): void { $middleware->alias([ 'role' => EnsureUserHasRole::class, 'agency' => EnsureAgencyIsActive::class, ]); }) ``` Three details matter: - `alias()` **assigns** the custom map rather than merging into it. Calling it twice, for example once per feature, leaves only the second call's aliases. Put every custom alias in one array. - The final alias map is the defaults merged with your array, **yours winning**. Registering `'auth' => MyAuth::class` silently replaces the framework's `auth` everywhere, including in packages that rely on it. - An alias is just a lookup. It does not register the middleware anywhere; a route, group or controller still has to use it. ## Passing parameters from a route The parameter syntax is `name:first,second`: ```php Route::put('/bookings/{booking}/refund', RefundBookingController::class) ->middleware('role:admin,agent'); ``` Two components cooperate to deliver them: 1. `Illuminate\Routing\MiddlewareNameResolver` splits the string on the **first colon**, looks up `role` in the alias map and rebuilds `App\Http\Middleware\EnsureUserHasRole:admin,agent`. 2. When the request runs, `Illuminate\Pipeline\Pipeline` splits that string again, resolves the class from the container, splits the parameter part on **commas**, and calls `handle($request, $next, 'admin', 'agent')`. The middleware declares the extra parameters after `$next`, typically as a variadic: ```php public function handle(Request $request, Closure $next, string ...$roles): Response ``` What this implies: - Parameters are **strings**. `'role:1'` passes the string `'1'`, not an integer, and `'true'` is not a boolean; cast inside the middleware. - Parameters are **not trimmed**. `'role:admin, agent'` passes `'admin'` and `' agent'` with a leading space, which then fails an equality check. - Parameters are **positional**. There is no key-value syntax; order in the string is order in the signature. - The alias is optional: `EnsureUserHasRole::class.':admin,agent'` works the same way. ## Parameters inside groups and exclusions A group entry can carry parameters too, for example `'role:agent'` inside a `back-office` group; the resolver keeps them when it expands the group. Excluding a parameterised middleware with `withoutMiddleware()` matches the **resolved string including its parameters**, so the exclusion has to name `role:agent`, not just `role`. ## Alias or class name? Both forms are legitimate, and teams tend to mix them. The trade-offs are practical rather than technical: | Aspect | Alias (`'role:admin'`) | Class (`EnsureUserHasRole::class.':admin'`) | |---|---|---| | Readability in route files | Short, reads like a rule | Longer, but explicit | | IDE navigation and refactoring | The string is opaque to most tools | Click-through and rename work | | Typo behaviour | Unknown alias surfaces as `Target class [rol] does not exist.` at request time | An unknown class can be caught by static analysis | | Needs registration | Yes, in `alias()` | No | A common compromise is to alias only middleware that take parameters or appear on many routes, and reference everything else by class. Whichever you choose, the resolved form is the same string, `Class:params`, so exclusions, priority sorting and `route:list` output behave identically. ## Travel-agency example On a travel-agency site, refunds may be allowed for admins and agents, while commission reports are admin-only. With one alias the route file stays declarative: `role:admin,agent` on the refund route, `role:admin` on the reports route. Adding a new role later touches the route strings, not the middleware class.
- What happens if two service areas of the app each call $middleware->alias() inside withMiddleware()?Only the last call's array survives, because `alias()` assigns the custom alias map instead of merging into it. A route using an alias from the first call then passes the bare name to the container, which throws `BindingResolutionException` (`Target class [role] does not exist.`). Keep all custom aliases in one `alias()` call.
- How would the role middleware receive its parameters if the route used the class name instead of an alias?Exactly the same way: `->middleware(EnsureUserHasRole::class.':admin,agent')`. The resolver only swaps an alias for its class; the colon and comma syntax is parsed by the pipeline regardless, so `handle()` still gets `'admin'` and `'agent'` after `$next`.
saying these in an interview costs you the question
- Expects middleware parameters to arrive as typed booleans or integers
- Thinks the parameters arrive as one comma-joined string
- Calls alias() once per feature and expects the maps to merge
- Believes parameters are passed through the middleware constructor
- Assumes spaces after the commas are trimmed away