Apply Middleware to all routes except `setup/*` in Laravel 5.4

28,856

Solution 1

There's nothing wrong with what you're doing, however, I would suggest splitting your route groups up instead i.e.:

Route::group(['middleware' => ['auth', 'checkOnboarding']], function () {
    Route::get('/home', 'HomeController@index');
    Route::get('/account', 'AccountController@index');
});

Route::group(['prefix' => 'setup', 'middleware' => 'auth'], function () {
    Route::get('/', 'OnboardingController@index')->name('setup');
    Route::post('/settings', 'SettingsController@store');
});

Alternatively, have a parent group for your auth:

Route::group(['middleware' => 'auth'], function () {

    Route::group(['middleware' => 'checkOnboarding'], function () {
        Route::get('/home', 'HomeController@index');
        Route::get('/account', 'AccountController@index');
    });

    Route::group(['prefix' => 'setup'], function () {
        Route::get('/', 'OnboardingController@index')->name('setup');
        Route::post('/settings', 'SettingsController@store');
    });
});

This will also mean you can remove the extra condition in your middleware:

/**
 * Check to see if the user has completed the onboarding, if not redirect.
 * Also checks that the requested URI isn't the setup route to ensure there isn't a redirect loop.
 */
return $request->user()->onboarding_complete ? $next($request) : redirect('setup');

Hope this helps!

Solution 2

You can utilize the Controller class for this with pretty spectacular results.

If you create a __construct function inside of HTTP/Controllers/Controller.php then you can declare middleware to run on every controller action and even declare exceptions as needed.

class Controller extends BaseController
{
  use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
  public function __construct(){
    $this->middleware('auth',['except' => ['login','setup','setupSomethingElse']]);
  }
}

Be careful not to put any of the standard index, store, update, destroy functions in the exception or you'll open up potential security issues.

Solution 3

Since Laravel 7.7 you can use excluded_middleware like this:

Route::group(['middleware' => ['auth','checkOnboarding']], function () {
    Route::get('/home', 'HomeController@index');
    Route::get('/account', 'AccountController@index');

    Route::group([
      'prefix' => 'setup',
      'excluded_middleware' => ['checkOnboarding'],
], function () {
        Route::get('/', 'OnboardingController@index')->name('setup');
        Route::post('/settings', 'SettingsController@store');
    }); 
});

Solution 4

There are 2 ways to go over this problem

  1. Try screening your routes in routes file web.php or api.php
  2. skip routes in middleware

In case of global middleware (middleware that you want to run before all routes), you should go with skipping routes in middleware.

For example:

//add an array of routes to skip santize check
protected $openRoutes = [
    'setup/*',
];

/**
 * Handle an incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @return mixed
 */
public function handle($request, Closure $next)
{
    if(!in_array($request->path(), $this->openRoutes)){
       //middleware code or call of function
    }       

    return $next($request);
}

For other middleware, you can easily skip in routes file and group routes based on your middleware.

For example:

Route::group(['middleware' => 'checkOnboarding'], function () {
        Route::get('/home', 'HomeController@index');
        Route::get('/account', 'AccountController@index');
    });

Route::group(['prefix' => 'setup'], function () {
    Route::get('/', 'OnboardingController@index')->name('setup');
    Route::post('/settings', 'SettingsController@store');
});

Solution 5

In Laravel 8.x you can also use the withoutMiddleware() method to exclude one or many route to a group middleware

Route::middleware('auth')->group(function () {
    Route::get('/edit/{id}',[ProgramController::class, 'edit'])->name('edit');

    Route::get('/public', [ProgramController::class, 'public'])
          ->name('public')->withoutMiddleware(['auth']);
});

Check also the official doc: Here

Share:
28,856
Andy Holmes
Author by

Andy Holmes

Passionate about design and development. Love being thrown in at the deep & learning hands on.

Updated on July 15, 2022

Comments

  • Andy Holmes
    Andy Holmes almost 2 years

    I'm experimenting with Middleware in my Laravel application. I currently have it set up to run on every route for an authenticated user, however, I want it to ignore any requests that begin with the setup URI.

    Here is what my CheckOnboarding middleware method looks like:

    public function handle($request, Closure $next)
    {
        /** 
        * Check to see if the user has completed the onboarding, if not redirect.
        * Also checks that the requested URI isn't the setup route to ensure there isn't a redirect loop.
        */
        if ($request->user()->onboarding_complete == false && $request->path() != 'setup') {
            return redirect('setup');
        } else {
            return $next($request);
        }
    }
    

    This is being used in my routes like this:

    Route::group(['middleware' => ['auth','checkOnboarding']], function () {
        Route::get('/home', 'HomeController@index');
        Route::get('/account', 'AccountController@index');
    
        Route::group(['prefix' => 'setup'], function () {
            Route::get('/', 'OnboardingController@index')->name('setup');
            Route::post('/settings', 'SettingsController@store');
        }); 
    });
    

    Now, if I go to /home or /account I get redirected to /setup as you would expect. This originally caused a redirect loop error hence why & $request->path() != 'setup' is in the Middleware.

    I feel like this is a really clunky way of doing it, and obviously doesn't match anything after setup like the setup/settings route I have created.

    Is there a better way to have this Middleware run on all routes for a user, but also set certain routes that should be exempt from this check?