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?
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!
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.
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');
});
});
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
There are 2 ways to go over this problem
Try screening your routes in routes file web.php or api.php
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');
});
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
});
Related
I am working on a Laravel 8 app that uses Microsoft Azure for user management (login included).
I began by following this tutorial on their website.
I have these routes "under" the dashboard route, that I want to protect with a piece of custom middleware:
// Dashboard routes
Route::get('/dashboard', [DashboardContoller::class, 'index'])->name('dashboard');
Route::group(['prefix' => 'dashboard' , 'middleware' => ['checkSignedIn']], function() {
Route::get('/users', [UsersContoller::class, 'index']);
Route::get('/create-user', [UsersContoller::class, 'create']);
Route::get('/delete-user/{id}', [UsersContoller::class, 'delete']);
});
The conditions for a user to be allowed to the application's dashboard are:
They sign in with a valid Microsoft account
Their email is inside an aray of alowed emails:
private $allowedEmails = [
'user.one#domain.com',
'user.two#domain.com',
'user.three#domain.com',
];
For this purpose, I have done the flollowing:
Created a CheckSignedIn middleware, with php artisan make:middleware CheckSignedIn.
Registered the above middleware in app\Http\Kernel.php:
protected $routeMiddleware = [
// More middleware
'checkSignedIn' => \App\Http\Middleware\CheckSignedIn::class,
];
In app\Http\Middleware\CheckSignedIn.php I have:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckSignedIn {
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* #return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
private $allowedEmails = [
'user.one#domain.com',
'user.two#domain.com',
'user.three#domain.com',
];
public function handle(Request $request, Closure $next) {
$isSignedIn = null !== session('userName') && in_array(session('userEmail'), $this->allowedEmails);
if (!$isSignedIn) {
return redirect('/');
}
return $next($request);
}
}
The problem
Evan if I am not logged in I can still see the dashboard (the /dashboard route).
Shouldn't this line deal with the /dashboard route too?
Route::group(['prefix' => 'dashboard' , 'middleware' => ['checkSignedIn']], function() {
What am I doing wrong?
Change your routes like this:
// Dashboard routes
Route::group(['prefix' => 'dashboard', 'middleware' => ['checkSignedIn']], function() {
Route::get('/', [DashboardContoller::class, 'index'])->name('dashboard');
Route::get('/users', [UsersContoller::class, 'index']);
Route::get('/create-user', [UsersContoller::class, 'create']);
Route::get('/delete-user/{id}', [UsersContoller::class, 'delete']);
});
I have a Middleware for ajax-only routes.
// AjaxOnly Middleware class
public function handle($request, Closure $next)
{
if (!$request->ajax()) {
// dd('I\'m (condition) working as expected!');
return response()->view('layouts.app');
}
dd('I never work!');
return $next($request);
}
Here is my routes web.php
// Ajax only routes
Route::group(['middleware' => 'ajaxOnly'], function () {
// Work an print 'false'
// dd(Request::ajax());
// Redirect me to /login page
Route::group(['middleware' => ['auth:user']], function () {
Route::get('/', 'HomeController#index')->name('home');
});
// Authentication routes
Auth::routes();
});
So, can someone explain why the code continues to run inside Middleware-protected closure? Thanks.
UPD: Just clarify -
Expected behavior: layouts.app in my browser.
Real behavior: redirect to login page.
You can adjust your middleware priority to ensure the correct middleware executes in the correct order. You can achieve this by overriding the default $middlewarePriority in your Kernel.php file.
/**
* The priority-sorted list of middleware.
*
* Forces the listed middleware to always be in the given order.
*
* #var array
*/
protected $middlewarePriority = [
\Illuminate\xxx\Middleware\AjaxOnly::class,
\Illuminate\xxx\Middleware\Auth::class,
];
You can follow what Illuminate\Routing\Router does with $middlewarePriorty here in the source code.
I want to create my app ( I dont want to use laravel default login system)
I want to use a middleware to be run during every HTTP request in my application except one
in laravel 5.1 documention syas I can use Global Middleware but I want to not use middleware for just login page.
what should I do ?
this is my middleware :
<?php
namespace App\Http\Middleware;
use Closure;
class Admin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if( ! session()->has('Login' ) )
{
return redirect('login');
}
return $next($request);
}
}
You can use routes group and assign your middleware to it:
Route::group(['middleware' => 'Admin'], function () {
// All of your routes goes here
});
// Special routes which you dont want going thorugh the this middleware goes here
Don't do anything to your middleware. you have the free to take that route outside the route group. so it becomes a standalone route. Or you can create a new route group and put only that one route in without that middleware. eg.
Route::group(['prefix' => 'v1'], function () {
Route::post('login','AuthenticationController');
});
Route::group(['prefix' => 'v1', 'middleware' => 'web'], function () {
Route::resource('deparments','AuthenticationController');
Route::resource("permission_roles","PermissionRolesController");
});
with this the middleware affect only the second route group
There are a couple of ways to tackle this, one is to address this in your middleware and exclude the route there, and two is to group all the routes you want to have covered by the middleware in your routes.php and then have the ones you want excluded outside of the grouping.
Tackling this in middleware
Just amend the handle function to include an if statement checking the URI requested
public function handle($request, Closure $next)
{
if ($request->is("route/you/want/to/exclude"))
{
return $next($request);
}
if( ! session()->has('Login' ) )
{
return redirect('login');
}
else
{
return redirect('login');
}
}
This method allows you to set the middleware up as global middleware and you can make multiple exclusions by extending the if statement with or $request->is().
Tackling this in routes
//Place all the routes you don't want protected here
Route::group(['middleware' => 'admin'], function () {
//Place all the routes you want protected in here
});
I am having trouble with Laravel routes. I'm trying to redirect to a controller after some middleware in the routes. But there is always this error.
The error is:
InvalidArgumentException in UrlGenerator.php line 558: Action
App\Http\Controllers\DashboardController#index not defined.
The route code is:
Route::get('/dashboard', ['middleware' => 'auth', function() {
return Redirect::action('DashboardController#index', array('user' => \Auth::user()));
}]);
The controller:
class DashboardController extends Controller
{
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
return view('dashboard')->with('user', \Auth::user());
}
}
But the above code actually works (so I guess that the controller actually works):
Route::get('/testdashboard', [
'uses' => 'DashboardController#index'
]);
So what is the problem? What is a valid route action?
This is might a better way to do it, change from
Route::get('/dashboard', ['middleware' => 'auth', function() {
return Redirect::action('DashboardController#index',
array('user' => \Auth::user()));
}]);
to
Route::get('/', [
'middleware' => 'auth',
'uses' => 'DashboardController#index'
]);
This is rather a comment than a post, but I can't send it at this time. I don't undestand why do you pass parameter (\Auth:user()) to a method that doesn't require it (but it's correct when you do it for the View).
Anyways I suggest you to work on your Middleware
public function handle($request, Closure $next)
{
if (Auth::check()) {
return redirect(...);
} else {
return redirect(...);
}
}
Use this route in place of your route and upgrade your Laravel Project to Laravel 8:
Route::middleware(['auth:sanctum', 'verified'])->group(function () {
Route::get('/dashboard', 'DashboardController#index')->name('daskboard');
});
Today I tried to make some changes to my application. I tried to pass all pages through authentication first. I tried one of the answer on this site. But that didn't help. Please help. Here's my code in routes.php
<?php
Route::get('/', function(){
return view('homepage');
});
Route::controllers([
'auth' => 'Auth\AuthController',
'password' => 'Auth\PasswordController',
]);
Route::group(['before' => 'auth'], function () {
Route::get('home', function(){
return \Redirect::to('twitter');
});
Route::get('twitter', 'HomeController#index');
.
.
.
.
});
There are several routes in my file. But only twitter route works.
In laravel 5,
['before' => 'auth']
is deprecated. But instead, I should use
['middleware' => 'auth']
Laravel5 has middlewares instead of filter. I assume you are trying to show certain page to only guests and for that we have a guest middleware already built in.
Route::group(['middleware' => 'guest'], function () {
Route::get('home', function(){
return \Redirect::to('twitter');
});
Route::get('twitter', 'HomeController#index');
});
you may also use middlewares on particular functions of your controller if you want, e.g
class MyController extends Controller {
public function __construct()
{
//to add auth middleware only on update method
$this->middleware('auth', ['only' => 'update'])
//to add auth middleware on all fucntions expcept login
$this->middleware('auth', ['except' => 'login'])
}
}