Laravel Middleware, ACL, Logic and Uses

Laravel Middleware


Laravel is one of my favourite setups out there at the moment. It's easy to use and simple to get most things working. The documentation is fairly good and there's very little reason not to try it out.

I have recently started to use middleware in one of my projects which I should have probably figured out a while back but never mind.

What is Middleware?


Middleware, in its simplest form is scripts which run before an event / function within a Controller class.

One of the most commonly used middlewares shipped with Laravel is auth. This middleware determines if a user is logged in and if not, redirects the user to the login page.

So why do I need middleware?


Well you probably, like me, have some kind of tiered access control requirement which may make you design an elaborate role based system to check if a user is allowed to visit certain routes, controllers and/or their functions.

I have developed an automated login system utilising WIA (Windows Integrated Authentication) and SimpleSAML but thats a blog for another day. The reason I mention this is that I am utilising Active Directory (AD) groups within the application and these are the groups I will be demonstrating for middleware.

Whats first?

Create a middleware and register it.

Load up a command line (windows or linux, whichever your setup with)

php artisan make:middleware ACLMiddleware

You can call it what you like, ACL for example, but I like to follow conventions used elsewhere in Laravel, controllers with controller at the end etc.

Next you will need to register it in the kernel.php file. This is located at:

app > Http > Kernel.php

Look for the $routedMiddleware array variable. This will contain auth already and a few others.

Add our new middleware here like so:

protected $routeMiddleware = [
    ...
    'ACL' => \App\Http\Middleware\ACLMiddleware::class,
    ...
];

Now we have middleware created and registered, logic is required for it to actually make any difference. In its current state, you can call it but it would do nothing. Literally nothing.

So how to make it interactive?

So I am going to utilise my setup here so you can see something working. Just modify to fit your own group control system.

User.php model file


public function groupmemberships(){
    return $this->hasMany('App\GroupMembership', 'userID', 'id');
}
public function hasRole($role){
    return (in_array($role, json_decode($this->groupmemberships
            ->pluck('groupInfo')->pluck('name'), TRUE)));
}

GroupMembership.php model file

public function groupInfo(){
    return $this->hasOne('App\Groups', 'id', 'groupID');
}

The database for the 3 models listed from above is setup as per this picture:






Here is the DDL for your convenience. Modify to suit:

create table groupmembership
(
id int unsigned auto_increment
primary key,
userID int not null,
groupID int not null,
created_at timestamp null,
updated_at timestamp null
)
collate=utf8mb4_unicode_ci;
create table `groups`
(
id int unsigned auto_increment
primary key,
name varchar(255) not null,
created_at timestamp null,
updated_at timestamp null,
constraint groups_name_uindex
unique (name)
)
collate=utf8mb4_unicode_ci;
create table users
(
id int unsigned auto_increment
primary key,
uniqueID varchar(255) not null,
FirstName varchar(255) not null,
LastName varchar(255) not null,
uid varchar(255) not null,
email varchar(255) not null,
remember_token varchar(100) null,
created_at timestamp null,
updated_at timestamp null,
constraint users_uniqueid_unique
unique (uniqueID)
)
collate=utf8mb4_unicode_ci;

Now you have the database schema, models and middleware setup, all you need is the logic.

Middleware logic


Well all you have to do is add the logic within the handle() function AND add $role to the list of parameters.

This should now look like this:

public function handle($request, Closure $next, $role){
    if(!$request->user()->hasRole($role))
        return redirect()->to(Route('home'))->withErrors('Unauthorised.');    return $next($request);
}

How do I use middleware now?


This is alot easier than it might seem now all that has been setup.

So in any controller, you can now call middleware checks.
You can call middleware on routes and you can include it in route groups too.
Its also possible to limit to one or some functions or exclude it from specific functions within a controller.

From a controllers constructor, replace <rolename> with the actual roles name:

$this->middleware('ACL:<rolename>');

From a route:

Route::get('/user/details', function (){ } )->name('user.details')->middleware('ACL:Staff');

From a route group:

Route::middleware(['ACL:Staff', 'auth'])->group(function () { Route::get('/', function () { }); Route::get('/user/details', function () { });});

Multiple middleware:

$this->middleware(['ACL:Staff'', 'auth']);

Function exclusion:

$this->middleware(['ACL:Staff'', 'auth'])->except('functionName', 'functionTwo);

Function inclusion:

$this->middleware(['ACL:Staff'', 'auth'])->only('functionName', 'functionTwo');

Is there anything I need to know?

This is Laravel 5.5 so I cannot vouch for it being changed in the future. I would expect it to work very similarly for some time to come while mostly new features are added to laravel rather than existing features being completely changed. Enjoy having an ACL middleware attached to your laravel application!

If you do utilise anything from this guide and upload to github, I would appreciate a comment in your code linking to this blog!

Comments

Adam W said…
Thank you for your comment ExpressTech. I am sorry I missed your comment, but I don't exactly have many on my blogs! I have checked and unfortunately even if I could attend, I may not be able to as I am in the UK.