I started on a tool a while back to “scratch an itch” in a personal project to make it easier to protect endpoints based on the requested URL. The Invoke library makes it possible to detect the route requested and ensure a set of criteria are met to be sure a user can access a resource. This isn’t anything new or revolutionary, but it is something I couldn’t find separate and effectively decoupled from other tools. It’s loosely based on how Symfony (v1) does it’s route protection, right down to the .yaml
file that it uses for configuration. In my case, I was using it in a Slim framework-based application to evaluate the current user to see if they had the required groups and permissions.
More recently, though, I’ve been messing with Laravel and since they’ve been putting a heavy emphasis on middleware, I thought I’d see how tough an integration might be. I wanted to use Invoke to check my Laravel user to see if they met my requirements. Fortunately, it turned out to be super easy.
Here’s how I did it – hopefully it can be useful for you. I’ll provide one caveat though: Laravel’s default auth handling only sets up users, not groups/permissions, so you’ll need a way to manage those but that’s outside the scope of this. You’ll see how it integrates in a bit.
First off, we need to get Invoke
installed via Composer:
composer require psecio/invoke
Once that’s installed, we need to set up our middleware. This will go in with the rest of the default middleware in the app/Http/Middleware
folder in your application. Create a file called InvokeMiddleware.php
with this code:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Auth as Auth; class InvokeMiddleware { /** * Handle an incoming request and validate against Invoke rules * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $en = new \Psecio\Invoke\Enforcer(app_path().'/../config/routes.yml'); $user = Auth::user(); $allowed = $en->isAuthorized( new \App\InvokeUser($user), new \Psecio\Invoke\Resource($request->path()) ); if ($allowed !== true) { return response('Unauthorized.', 401); } return $next($request); } }
You’ll see there’s a reference to an InvokeUser
class in there. Let’s make that next. In app/InvokeUser.php
put this code:
<?php namespace App; class InvokeUser implements \Psecio\Invoke\UserInterface { private $user; public function __construct($user) { $this->user = $user; } public function getGroups() { // This is where you fetch groups return []; } public function getPermissions() { // This is where you fetch permissions return []; } public function isAuthed() { return ($this->user !== null); } }
Then, to turn it on, edit your app/Http/Kernel.php
class and add this to the list of $middleware
:
\App\Http\Middleware\InvokeMiddleware::class,
Viola, you’re set to go using Invoke
. Now, the next step is to define your rules. You’ll notice in the middleware above we’re loading the config/routes.yml
configuration file for our rules. Let’s make one of those with a super simple example. In app/config/routes.yml
put:
/: protected: on
This configuration is telling Invoke
that, when you hit the base URL of your application (“/”) it’s protected. This means that it requires a logged in user. If you’ve just added this to your application and try to load the main page without a user, you’ll be given the unhappy “Forbidden” message instead of your lovely site. It’s the check in InvokeUser::isAuthed
that evaluates for this, checking to see if the user is null (no valid logged in user).
That’s it…it’s a pretty simple integration to get just get bare minimum up and running. If you’re interested in how to add group and permission checking to this, forge ahead and keep reading.
So we have our basic yaml
configuration file with protection turned on. Say we wanted to add in group and permission checks too. I’ve already talked some about this kind of handling in a different post but I’ve more recently simplified it even more, no longer requiring extra classes in the mix.
Let’s start by changing our configuration file to tell Invoke
that we want to be sure the user is in the “admin” group and has a permission of “delete_user” to access the /admin/user/delete
resource:
/admin/user/delete: protected: on groups: [admin] permissions: [delete_user]
When you fire off the page request for that URL, Invoke
will try to call the InvokeUser::getGroups
and InvokeUser::getPermissions
methods to return the user’s current permission set. Before it required you to use classes that implemented the InvokeGroup
and InvokePermission
interfaces for each group/permission. I streamlined this since it’s really only evaluating string matches and allowed those methods to either return a set of objects or of strings. Let’s update the InvokeUser
class to hard-code in some groups/permissions for return:
<?php namespace App; class InvokeUser implements \Psecio\Invoke\UserInterface { /** ...more code... */ public function getGroups() { return ['admin','froods']; } public function getPermissions() { return ['delete_user','view_user','update_user']; } /** ...more code... */ }
Ideally you’d be fetching these groups and permissions from some role-based access control system (maybe, say Gatekeeper) and returning real values. These hard-coded values will work for now.
Since the user has all the requirements, Invoke
is happy and they’re able to move along and delete all the users they want.
I’ve tried to keep the class as simple as possible to use and I’m definitely open to suggestions. There’s a few additions I’d though about including adding HTTP method matching (different rules for POST than GET) and other match types than just groups and permissions. Let me know if you’d like to see something else included in Invoke
– I’d love to chat!
This looks interesting. I think one of the biggest headaches I have found with rapid development is that permissions tend to get half baked or engineered in a way that tends not to be entirely DRY. Invoke looks to be something that can be baked in from any point in a projects development cycle and allows for quick and easy management of permissions without touching a line of code.
LikeLike
Agreed…authentication and authorization are hard problems to solve. Anyone that tells you any different is selling you something 🙂
LikeLike