Andy Holmes
Andy Holmes

Reputation: 8087

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

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?

Upvotes: 20

Views: 38937

Answers (6)

Mateusz Przybylek
Mateusz Przybylek

Reputation: 5925

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');
    }); 
});

Upvotes: 8

MUMBERE MALULE Jacques
MUMBERE MALULE Jacques

Reputation: 140

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

Upvotes: 7

Ankit Jindal
Ankit Jindal

Reputation: 4050

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');
});

Upvotes: 3

Rwd
Rwd

Reputation: 35220

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!

Upvotes: 21

bstory
bstory

Reputation: 862

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.

Upvotes: 14

Abhay Maurya
Abhay Maurya

Reputation: 12277

Routes on which you dont want the middleware to run , simply put them outside of the function:

//here register routes on which you dont want the middleware: checkOnboarding
Route::group(['middleware' => ['auth','checkOnboarding']], function () {
     //routes on which you want the middleware
});

Upvotes: 1

Related Questions