Reputation: 8087
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
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
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
Reputation: 4050
There are 2 ways to go over this problem
web.php or api.php
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
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
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
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