Web Frameworks [OGI06i]

07. Middleware

07.1
Middleware

Middleware Introduction

  • Middleware = code executed before or after a request is handled by the application
  • Middlewares can be
    • global (= run for every HTTP request)
    • applied to a specific route (group) or controller
    • part of a middleware group, like the predefined web and api groups
  • Laravel offers pre-built middlewares (inside the Laravel library) e.g.
    • authentication
    • CSRF protection
  • You can define your own middleware

Defining your own Middleware

  • First step: generate the middleware with Artisan (location: app/Http/Middleware)
    $ php artisan make:middleware EnsureTokenIsValid
  • Example of Before Middleware
    namespace App\Http\Middleware;
    use Closure;
    use Illuminate\Http\Request;
    use Symfony\Component\HttpFoundation\Response;
    
    class EnsureTokenIsValid
    {
        public function handle(Request $request, Closure $next): Response
        {
            if ($request->input('token') !== 'my-secret-token') {
                return redirect('home');
            }
            return $next($request);
        }
    }

Before vs. After Middleware

class BeforeMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        // Perform action

        return $next($request);
    }
}
class AfterMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        // Perform action

        return $response;
    }
}

Registering Global Middleware

  • In bootstrap/app.php, the 7 default global middlewares are loaded by
    ->withMiddleware(function (Middleware $middleware) {})
  • In order to add your own global middleware, replace it by
    ->withMiddleware(function (Middleware $middleware) {
         $middleware->append(EnsureTokenIsValid::class); // prepend = add it to the beginning of the list
    })
  • Or manage the global middleware stack yourself:
    ->withMiddleware(function (Middleware $middleware) { // this is the default stack (!)
        $middleware->use([
            \Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class,
            // \Illuminate\Http\Middleware\TrustHosts::class,
            \Illuminate\Http\Middleware\TrustProxies::class,
            \Illuminate\Http\Middleware\HandleCors::class,
            \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
            \Illuminate\Http\Middleware\ValidatePostSize::class,
            \Illuminate\Foundation\Http\Middleware\TrimStrings::class,
            \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        ]);
    })

Middleware Aliases

  • Some of Laravel's built-in middleware have aliases by default:
Alias Middleware
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
subscribed \Spark\Http\Middleware\VerifyBillableIsSubscribed
throttle Illuminate\Routing\Middleware\ThrottleRequests or Illuminate\Routing\Middleware\ThrottleRequestsWithRedis
verified Illuminate\Auth\Middleware\EnsureEmailIsVerified

Registering specific middleware (1)

  • Option 1: plug the middleware to a specific route
    Route::get('admin/profile', 'UserController@showProfile')->middleware('auth');
    Route::get('/', function () {
        //
    })->middleware(['first', 'second']);
    or, without aliases:
    Route::get('admin/profile', 'UserController@showProfile')->middleware(Authenticate::class);
    Route::get('/', function () {
        //
    })->middleware([First::class, Second::class]);

Registering specific middleware (2)

  • Option 2: plug the middleware to a route group
    Route::middleware(['first', 'second'])->group(function () {
        Route::get('/', function () {
            // Uses first and second Middleware
        });
    
        Route::get('user/profile', function () {
            // Uses second Middleware
        })->withoutMiddleware(['first']);
    });

Registering specific middleware (3)

  • Option 3: plug the middleware to a specific controller
    • Implement the HasMiddleware interface
    • The static function middleware() returns any middlewares that should be applied to the controller's actions
    class TaskController extends Controller implements HasMiddleware
    {
        public static function middleware(): array
        {
            return [
                'auth',
                new Middleware('log', only: ['index']),
                new Middleware('subscribed', except: ['store']),
            ];
        }
    }

Middleware Groups (1)

  • Group several middleware under a single key
  • In bootstrap/app.php, the 2 default middleware groups web and api are loaded by
    ->withMiddleware(function (Middleware $middleware) {})
  • The web group applies to any route in routes/web.php
  • The api group applies to any route in routes/api.php
  • Make your own group or add your middleware to an existing group by:
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->appendToGroup('my-group', [ // you can also prepend, replace and remove
            First::class,
            Second::class,
        ]);
    
        $middleware->web(append: [ // shorthand for appendToGroup('web', ...
            EnsureUserIsSubscribed::class,
        ]);
    })

Middleware Groups (2)

  • Assign to routes and controller actions using the same syntax as individual middleware:
    Route::get('/', 'TaskController@showTask')->middleware('my-group'); // this is just 1 of the options
  • Customize the default groups yourself in bootstrap/app.php:
    ->withMiddleware(function (Middleware $middleware) { // these are the group defaults (!)
        $middleware->group('web', [
            \Illuminate\Cookie\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
        ]);
    
        $middleware->group('api', [
            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            // 'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ]);
    })

Middleware Parameters

  • Check this interesting example (simple authorization)
    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 $role): Response
        {
            if (! $request->user()->hasRole($role)) {
                // Redirect...
            }
            return $next($request);
        }
    }
  • Pass your parameters (separated by ,) by adding a : after the middleware name
    Route::post('blogposts/create', 'PostController@store')->middleware(EnsureUserHasRole::class.':editor');
    Route::post('blogposts/create', 'PostController@store')->middleware('role:editor');
In the next course, you will see Gates and Policies for Authorization and you will you need use those.

07.2
Outro

Outro

  • This was the Laravel part of Web Frameworks for now
  • Next course: Full-stack Advanced. Laravel topics:
    • Authentication (main principles)
    • Authorization with Gates and Policies
    • Web API authentication with Laravel Sanctum
    • Testing
    • Deployment
    • Some pointers to Mail, Task Scheduling, …
    • GraphQL (to be confirmed)
    • Hybrid rendering

Questions?

Sources