Laravel 5.2 Controller middleware for more than one role - php

I have a roles administrator, moderator and member in my laravel application. Application have fronted and backend sections. I want to allow access to backend section only for administrator and moderator. I create SuperUsersMiddleware:
<?php
namespace CMS\Http\Middleware;
use Closure;
class SuperUsersMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (! $request->user()->hasRole('administrator') || ! $request->user()->hasRole('moderator')) {
return redirect('/');
}
return $next($request);
}
}
Register in Kernel.php:
......
protected $routeMiddleware = [
'superusers' => \CMS\Http\Middleware\SuperUsersMiddleware::class,
'administrator' => \CMS\Http\Middleware\AdminMiddleware::class,
'moderator' => \CMS\Http\Middleware\ModeratorMiddleware::class,
'auth' => \CMS\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
'guest' => \CMS\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
.....
and in my backend folder I create Controller.php (all other controllers in backend section extends this controller) and in __construct() function set middleware:
...
public function __construct()
{
$this->middleware('superusers');
}
...
But this doesn't work for me. I also create administrator and moderator middleware and it works separately but I needed both - together. How to do that? I tray:
public function __construct()
{
$this->middleware('administrator');
$this->middleware('moderator');
}
But this also can't work. What is a best practice for this situation?

First off I wouldn't apply any Middleware on your master Controller as then middleware would be applied to everything. You should do this on each individual controller like UserController.
You can apply as many middleware instances to a route/function as you want. I'm not aware of any limitations on that. So I'm not sure why you say this doesn't work:
public function __construct()
{
$this->middleware('administrator');
$this->middleware('moderator');
}
You can apply the different middleware to the routes that require the different levels. You can do this in your routes.php or in your Controllers. If you want to do it in your Controller like you are doing above you would have something like this:
public function __construct()
{
$this->middleware('auth'); //this applies to all actions
$this->middleware('administrator', ['only' => ['adminFunction', 'otherAdminFunction','bothCanAccess']]);
$this->middleware('moderator',['only' => ['moderatorFunction','bothCanAccess']);
}
public function adminfunction()
{
...
}
public function otherAdminfunction()
{
...
}
public function moderatorFunction()
{
...
}
public function bothCanAccess()
{
...
}
So first off the auth middleware will apply to all actions. This means a user has to be logged in to access any function in here. Then you can apply specific middleware to each function. If you need more info on this check out the documentation:
https://laravel.com/docs/5.2/controllers#controller-middleware
To do this in your router you would do something like this:
Route::get('/admin', ['middleware' => ['auth', 'administrator'],'uses'=>'Controller#adminFunction']);
So in this case it will apply the auth middleware first to make sure someone is logged in, then fire the administrator middleware and make sure the user is an admin.
Hopefully that helps.

Related

Laravel auth to check for column before logging in

I'm using Laravel 5.4 and its built in Auth API. I've added a column to the users table called is_admin. I want to modify the login process so that login only works if is_admin == 1. I've looked in Illuminate\Foundation\Auth\AuthenticatesUsers.php (Github) and I could probably make a change there but I'd rather not disturb the core if possible. Is there a more elegant way to do this?
I solved this with a solution similar to #Sagar Arora's. In the app\Http\Controllers\Auth\LoginController.php that is created when you run artisan make:auth, I overrode the attemptLogin method from AuthenticatesUsers. This is the code in LoginController.php:
protected function attemptLogin(Request $request)
{
return (auth()->attempt(['email' => $request->email, 'password' => $request->password, 'is_admin' => 1]));
}
Now only users with is_admin == 1 will be logged in.
Just like you mentioned, you should never touch the core file. But if you could see laravel has added the AuthenticatesUsers as trait on LoginController which means you can simply override it by adding the credentials() in LoginController like the following.
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(Request $request)
{
return [
'email' => $request->input('email'),
'password' => $request->input('password'),
'is_admin' => 1
];
}
This solution may not be elegant, but still working.
Add this to LoginController:
protected function authenticated(Request $request, $user)
{
if ( ! $user->is_admin ) {
Auth::logout();
return redirect('login')->withErrors(['is_admin' => 'Only admin users may log in']);
}
}
Then put this in auth/login.blade.php:
#if($errors->has('is_admin'))
<div class="alert alert-danger" role="alert">{{ $errors->first('is_admin') }}</div>
#endif
Also keep in mind, that you should disable register functionality, because after registering a user still logs in.
You can create your login function as:
So in your login function you can check this by:
if (Auth::attempt(['email' => $email, 'password' => $password, 'is_admin' => 1])) {
// The user is active, not suspended, and exists.
}
You check here manual login process with additional fields:
https://laravel.com/docs/5.4/authentication#authenticating-users
Thanks
I think a better way is to actually allow all users to login. And then limit access via admin middleware. This way you may apply this middleware to as many routes as you like.
Use it by wrapping necessary routes in
Route::group(['middleware' => 'admin'], function() {
// auth protected routes
});
This way you create app\Http\Middleware\RedirectIfNotAdmin:
<?php
namespace App\Http\Middleware;
use Closure;
class RedirectIfNotAdmin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$user = $request->user();
if ( $user && $user->is_admin ) {
return $next($request);
}
return redirect('/');
}
}
Then in app\Http\Kernel.php you append it to $routeMiddleware property:
protected $routeMiddleware = [
// ... other middlewares
'admin' => \App\Http\Middleware\RedirectIfNotAdmin::class
];

Laravel how to pass parameters to controller middleware

I'm trying to do something but i cant even imagine how to do it.
To pass parameters to a middlware I'm doing this:
Route::put('post/{id}', ['middleware' => 'role:editor', function ($id) {
//
}]);
From the laravel documentation.
Then get the parameter in the handle function...
But laravel suggest to declare the middleware use in the __construct in the Controller, insted in the route.
So...
public function __construct() {
$this->middleware('auth');
}
But i dont know how to pass parameters to the Controller.
I would thank any help.
You can access url parameters into middleware shown as below:
routes.php
Route::get('/test/{id}', ['uses' => 'Test#test']);
Test Controller
<?php
namespace App\Http\Controllers;
class Test extends Controller
{
/**
* Test constructor.
*/
public function __construct()
{
$this->middleware('test');
}
public function test()
{
return 'sample';
}
Test Middleware
<?php namespace App\Http\Middleware;
use Closure;
class Test
{
public function handle($request, Closure $next)
{
dd($request->id);
return $next($request);
}
}
** dont forget update your Kernel.php to activate middleware
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'test' => \App\Http\Middleware\Test::class,
];
Don't put the middleware in the controller. Keep business logic out of the controller as often as possible.
Instead, make your middleware file.
php artisan make:middleware MyMiddlewareDoesThings
Now inside of your file, locate the $handle function.
public function handle($request, Closure $next)
Just add arguments after the request and closure
public function handle($request, Closure $next, $role)
Now $role will contain the value of role:value which from your example would be $editor
#muhammad-sumon-molla-selim answer is actually the best one according to the question, but it leaks into explanation.
With my knowledge I was able to follow his guideline so here is a full example plus a usecase where Route's middleware or Request parameters is not possible.
I have an abstract CRUDControllerBase, which is extended by many child controllers.
Each child controllers need a different permission to perform any action on the model excepts index/show.
So I was forced to dynamically pass the permission (string) from the controller to the Middleware, here is the base structure:
// App\Http\Controllers\CRUDControllerBase.php
abstract class CRUDControllerBase extends Controller
{
/**
* #var string The needed permission to call store or update
* If null, permission is granted to every users
*/
protected ?string $permission = null;
public function __construct()
{
// Here I need to pass to VerifyUserPermission the controller's permission
$this->middleware(VerifyUserPermission::class)
->except(['index', 'show']);
}
}
// App\Http\Controllers\DocumentController.php
class DocumentController extends CRUDControllerBase
{
protected ?string $permission = 'documents';
}
// App\Http\Controllers\EventController.php
class EventController extends CRUDControllerBase
{
protected ?string $permission = 'events';
}
And here is how I tackled this following the #muhammad-sumon-molla-selim answer:
// App\Http\Middleware\VerifyUserPermission.php
// Create the VerifyUserPermission middleware, which accepts the permission string
class VerifyUserPermission
{
public function handle(Request $request, Closure $next, ?string $permission): mixed
{
$user = $request->user();
// If a permission is needed but the user doesn't have it, abort
if ($permission && !$user->$permissionField) {
abort(401);
}
return $next($request);
}
}
// App\Http\Kernel.php
// Register the middleware
class Kernel extends HttpKernel
{
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
// [...]
'permission' => \App\Http\Middleware\VerifyUserPermission::class
];
}
// App\Http\Controllers\CRUDControllerBase.php
// Then just register the middleware into the CRUDControllerBase's constructor
abstract class CRUDControllerBase extends Controller
{
protected ?string $permission = null;
public function __construct()
{
// 'documents' permission will be needed for Documents' edition
// 'events' permission will be needed for Events' edition
// And so on for many others controllers
$this->middleware("permission:$this->permission")
->except(['index', 'show']);
}
}
If you want to get the parameters in the __construct of your controller you could do this:
class HomeController extends \BaseController
{
public function __construct()
{
$this->routeParamters = Route::current()->parameters();
}
}

Redirect after authentication in Laravel 5

I would like to redirect users back to the page they were viewing before the login page after successful login in Laravel 5. Currently if the user writes a wrong username/password he is redirected to the login page again, with error displayed, and I would like to keep it that way. Also I want this to work when user clicks on login button on a page and also in the situation when user is redirected to login page automatically.
Seems to me that
protected $redirectTo = "/";
is the key thing here, but I don't know how to get it to "redirect to last page which isn't the login page". Any help is appreciated.
$url = URL::previous() != url('login') ? URL::previous() : null;
$previousUrl = old('previousUrl', $url);
Pass $previousUrl to your login view.
In your login view put previousUrl field: <input type="hidden" name="previousUrl" value="{{$previousUrl}}"/>
In your AuthController.php update constructor like that:
public function __construct()
{
$this->redirectTo = app('request')->input('previousUrl') ? : $this->redirectTo;
<...>
}
Not tested, but it should work.
use URL::previous() in your redirect or return redirect()->back(); once you know the login was succesful.
Also, you can save the url in the session and use it later at any time.
Laravel allows you to override the redirectPath() method in the AuthController which normally will use the redirectTo property, but you can set it to something dynamic.
public function redirectPath()
{
return request('redirect');
}
And in the login blade template place a hidden input:
<input type="hidden" name="redirect" value="{{ url()->previous() }}">
Try:
redirect()->intended();
Laravel 5.2
You must store the previous URL when the user enters your login page:
https://laravel.com/docs/5.2/helpers#method-url
Session is an option: https://laravel.com/docs/5.2/helpers#method-session
After that you should fill $redirectTo with that information when user tries to login: https://laravel.com/docs/5.2/authentication#included-authenticating
I wrote an example using a Middleware.
First you need to create one:
php artisan make:middleware BeforeLoginForm
Now we need to override the default route for Auth\AuthController#showLoginForm, so we can add our middleware to that route:
app/Http/routes.php
Route::auth();
$this->get('login', 'Auth\AuthController#showLoginForm')
->middleware('before.loginform');
Define an alias for our Middleware:
app/Http/Kernel.php
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'before.loginform' => \App\Http\Middleware\BeforeLoginForm::class,
];
Our middleware will store the previous URL using the session helper:
app/Http/Middleware/BeforeLoginForm.php
namespace App\Http\Middleware;
use Closure;
class BeforeLoginForm
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
session()->put('before_login_url', url()->previous());
return $next($request);
}
}
The constructor of our AuthController will change the redirectTo property with our previously stored URL:
app/Http/Controllers/Auth/AuthController.php
<?php
namespace App\Http\Controllers\Auth;
class AuthController extends Controller
{
/**
* Where to redirect users after login / registration.
*
* #var string
*/
protected $redirectTo = '/';
/**
* Create a new authentication controller instance.
*
* #return void
*/
public function __construct()
{
$redirectTo = session()->get('before_login_url');
if (!empty($redirectTo)) {
$this->redirectTo = $redirectTo;
}
$this->middleware($this->guestMiddleware(), ['except' => 'logout']);
}
For Laravel 5.0 (only what is different)
You should register your route like this:
app/Http/routes.php
Route::get('/', 'WelcomeController#index');
Route::get('home', 'HomeController#index');
Route::controllers([
'auth' => 'Auth\AuthController',
'password' => 'Auth\PasswordController',
]);
Route::get('auth/login', [
'middleware' => 'before.loginform',
'uses' => 'Auth\AuthController#getLogin'
]);
Then modify redirectTo on your AuthController:
app/Http/Controllers/Auth/AuthController.php
public function __construct(Guard $auth, Registrar $registrar)
{
$this->auth = $auth;
$this->registrar = $registrar;
$redirectTo = session()->get('before_login_url');
if (!empty($redirectTo)) {
$this->redirectTo = $redirectTo;
}
$this->middleware('guest', ['except' => 'getLogout']);
}
I could not test that, will do it later.
Pay attention because the value of the property "redirectTo" will influence not only the authentication but also the registration process.

Single Laravel Route for multiple controllers

I am creating a project where i have multiple user types, eg. superadmin, admin, managers etc. Once the user is authenticated, the system checks the user type and sends him to the respective controller. The middle ware for this is working fine.
So when manager goes to http://example.com/dashboard he will see the managers dashboard while when admin goes to the same link he can see the admin dashboard.
The below route groups work fine individually but when placed together only the last one works.
/***** Routes.php ****/
// SuperAdmin Routes
Route::group(['middleware' => 'App\Http\Middleware\SuperAdminMiddleware'], function () {
Route::get('dashboard', 'SuperAdmin\dashboard#index'); // SuperAdmin Dashboard
Route::get('users', 'SuperAdmin\manageUsers#index'); // SuperAdmin Users
});
// Admin Routes
Route::group(['middleware' => 'App\Http\Middleware\AdminMiddleware'], function () {
Route::get('dashboard', 'Admin\dashboard#index'); // Admin Dashboard
Route::get('users', 'Admin\manageUsers#index'); // Admin Users
});
I know we can rename the routes like superadmin/dashboard and admin/dashboard but i was wondering if there is any other way to achieve the clean route. Does anyone know of any anywork arounds ?
BTW i am using LARAVEL 5.1
Any help is appreciated :)
You can do this with a Before Middleware that overrides the route action's namespace, uses and controller attributes:
use Closure;
use Illuminate\Http\Request;
use Illuminate\Contracts\Container\Container;
use App\Http\Middleware\AdminMiddleware;
use App\Http\Middleware\SuperAdminMiddleware;
class AdminRoutingMiddleware
{
/**
* #var Container
*/
private $container;
public function __construct(Container $container)
{
$this->container = $container;
}
private static $ROLES = [
'admin' => [
'namespace' => 'Admin',
'middleware' => AdminMiddleware::class,
],
'super' => [
'namespace' => 'SuperAdmin',
'middleware' => SuperAdminMiddleware::class,
]
];
public function handle(Request $request, Closure $next)
{
$action = $request->route()->getAction();
$role = static::$ROLES[$request->user()->role];
$namespace = $action['namespace'] . '\\' . $role['namespace'];
$action['uses'] = str_replace($action['namespace'], $namespace, $action['uses']);
$action['controller'] = str_replace($action['namespace'], $namespace, $action['controller']);
$action['namespace'] = $namespace;
$request->route()->setAction($action);
return $this->container->make($role['middleware'])->handle($request, $next);
}
}
This way you have to register each route only once without the final namespace prefix:
Route::group(['middleware' => 'App\Http\Middleware\AdminRoutingMiddleware'], function () {
Route::get('dashboard', 'dashboard#index');
Route::get('users', 'manageUsers#index');
});
The middleware will convert 'dashboard#index' to 'Admin\dashboard#index' or 'SuperAdmin\dashboard#index' depending on current user's role attribute as well as apply the role specific middleware.
The best solution I can think is to create one controller that manages all the pages for the users.
example in routes.php file:
Route::get('dashboard', 'PagesController#dashboard');
Route::get('users', 'PagesController#manageUsers');
your PagesController.php file:
protected $user;
public function __construct()
{
$this->user = Auth::user();
}
public function dashboard(){
//you have to define 'isSuperAdmin' and 'isAdmin' functions inside your user model or somewhere else
if($this->user->isSuperAdmin()){
$controller = app()->make('SuperAdminController');
return $controller->callAction('dashboard');
}
if($this->user->isAdmin()){
$controller = app()->make('AdminController');
return $controller->callAction('dashboard');
}
}
public function manageUsers(){
if($this->user->isSuperAdmin()){
$controller = app()->make('SuperAdminController');
return $controller->callAction('manageUsers');
}
if($this->user->isAdmin()){
$controller = app()->make('AdminController');
return $controller->callAction('manageUsers');
}
}

Roles with laravel 5, how to allow only admin access to some root

I follow this tutorial : https://www.youtube.com/watch?v=kmJYVhG6UzM Currently I can check in my blade if user is a admin or not like this:
{{ Auth::user()->roles->toArray()[0]['role'] }}
HI ADMIN
#endif
How can I make my route only available for admin user?
You need to create a middleware for your route.
Use: php artisan make:middleware AdminMiddleware.
You will find in your middleware folder a new file with this name.
Put your logic in your middleware, e.g.
public function handle($request, Closure $next)
{
if(Auth::check())
{
return $next($request);
}
else
{
return view('auth.login')->withErrors('You are not logged in');
}
}
Once you have done your logic in your middleware, you can either call it in the route or make the middleware apply to all routes.
If you want to add it to all routes, go to Kernel.php and add it to the $middleware array, e.g.
protected $middleware = [
'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',
'Illuminate\Cookie\Middleware\EncryptCookies',
'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
'Illuminate\Session\Middleware\StartSession',
'Illuminate\View\Middleware\ShareErrorsFromSession',
'App\Http\Middleware\VerifyCsrfToken',
'App\Http\Middleware\AdminMiddleware',
];
If you want to add it to specific routes only, add it to the $routeMiddleware variable and add the alias to the route. E.g.
protected $routeMiddleware = [
'auth' => 'App\Http\Middleware\Authenticate',
'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
'admin' => 'App\Http\Middleware\AdminMiddleware',
];
You can then add it to a route, as a filter, e.g.
Route::get('admin/profile', ['middleware' => 'admin', function()
{
}]);
For additional info visit the docs:
http://laravel.com/docs/master/middleware
EDIT
An improvement on this would be to use variadic functions which was introduced in PHP 5.6
http://php.net/manual/en/migration56.new-features.php
Instead of having to make a middleware for each permission set you can do the following
PermissionMiddleware
namespace App\Http\Middleware;
use Closure;
use \App\Models\Role;
class PermissionMiddleware
{
// Pass parameters to this middleware
public function handle($request, Closure $next, ...$permitted_roles)
{
//Get a users role
$role = new Role;
$role_name = $role->getUserRoleByName();
foreach($permitted_roles as $permitted_role) {
if($permitted_role == $role_name) {
return $next($request);
}
}
return redirect()->back()->withErrors('You do not have the required permission');
}
}
Notice the ...$permitted_roles
Route::get('admin/profile', ['middleware' => 'PermissionMiddleware:Admin,Marketing', function()
{
}]);
You can now specify as many roles as required for one middleware rather than creating multiple by using middleware parameters
Docs
https://laravel.com/docs/5.3/middleware#middleware-parameters
Let's assume you have a column in your users table with isAdmin name which has a default value of 0 (false)
You can give special access using middleware in laravel like you give access to logged in users using auth middleware in laravel.
Now you need to create a middleware using the command :
php artisan make:middleware AdminMiddleware
In your Kernel.php you need to add this line to protected $routeMiddleware
'admin' => \App\Http\Middleware\AdminMiddleware::class,
In your middleware folder you have the AdminMiddleware file.
In that you need to put your logic
In this case this is how it might look like depending upon you
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RoleMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::user()->isAdmin == '1') // is an admin
{
return $next($request); // pass the admin
}
return redirect('/'); // not admin. redirect whereever you like
}
}
Now in your route you have to pass the url using this middleware
Here is how it might look like
Route::get('/iamanadmin', ['middleware' => 'admin', function() {
return view('iamanadmin');
}]);
use middleware and check for admin user.
Route::get('admin', ['middleware' => 'checkadmin', function()
{
}]);
now create middleware and validate admin user.

Categories