Releasing Bouncer: Roles and Permissions for Laravel apps

Published on by

Releasing Bouncer: Roles and Permissions for Laravel apps image

After many years in development, 56 releases, 1.3 million downloads, and over 2,800 unsolicited stars, Bouncer has finally reached version 1.0. It has been extremely reliable and stable for quite a while now, and is being used in production by countless apps worldwide.

This is a personal update, with some musings about my journey through the years - from inception to final release. For technical information on how to use Bouncer day to day, check out the extensive documentation or listen to me discuss it with Matt Stauffer on The Laravel Podcast.


What is Bouncer?

Before diving into my personal journey, here's a quick overview of what Bouncer is, and how it fits into the greater Laravel ecosystem.

Bouncer is an open source package for dynamically managing roles and permissions in the database, fully integrated with Laravel's Gate.

Without getting into detail, here's a short list of some of its key features:

...and much more. For details, check out the full documentation, or just glance over the cheat sheet.

The original idea for Bouncer

Back in August of 2015, Taylor added a new Authorization system in Laravel 5.2, called the Gate. This provided a nice API for defining permission checks in your app for various actions, through simple define callbacks and full on policies, as well as hooks throughout the system for checking permissions against what you've defined.

As soon as I started playing with this, I knew it will be the future of ACL for all Laravel apps. It's just so good. Taylor has this amazing sense for clear and intuitive APIs, and the Gate abstraction really brought that to light.

However, one thing was missing from the built-in authorization system: dynamic permissions, stored in the database. The way the gate is built, all checks are performed by hard-coded functions defined in your app, so there's no way to allow your admins to control any of it at runtime via some dashboard UI. As Taylor's original commit clearly states:

[The built-in gate] gives a structure to organizing logic that authorizes actions on entities. It does not make any determination on how "user roles" are stored

At the time, there were many other popular ACL systems which did support tweaking permissions at runtime, but they had one major drawback: they were all built prior to Laravel's Gate. They were completely separate systems; if you decided to use them, you would forgo all of the niceties and beautiful integrations that Laravel's gate offered.

So I decided to build an open source package that would get you the best of both worlds: dynamic DB-driven permissions, fully integrated with Laravel's gate. We made some improvements to the gate checks in Laravel 5.3, making it more streamlined and predictable, thus easier to store those abilities in the DB.

The Bouncer name and logo

The name Bouncer came to me pretty early on. A bouncer's duty is to provide security at the gate and to check people's permissions. So this was a very natural paring to the Gate in Laravel.

Interestingly, the logo designer I was working with at the time (who is not a native English speaker) did not get the reference. Here are some original logos he designed:

The two to the right are clearly inspired by a bouncing motion. Oof! Hopefully native speakers do get it right away.

After quickly clarifying the meaning of the word bouncer, we started iterating on actual bouncers. We tried friendly bouncers, threatening bouncers, bearded bouncers, square-jawed bouncers, and a ton of different variations. Here are just a few:

I'm quite fond of what we ultimately ended up with:

It exudes a strong sense of security, but the roundness makes it feel friendlier and less threatening.

Technical underpinnings

Bouncer's raison d'etre is to seamlessly integrate with Laravel's gate. To achieve that, I set out with a single goal in mind: you should only have to interact with Bouncer when assigning roles and abilities to users. For the actual authorization checks, Laravel's own hooks throughout the system should work automatically, without any special Bouncer syntax.

The way Bouncer hooks into Laravel's gate checks is pretty simple. The Gate lets you define a global before callback, which gets called before any other checks you've defined: if your before callback allows or disallows an action, no further checks will run.

While the before callback was originally intended for stuff like "allow everything for admins", I immediately realized that this would be the perfect place to hook up dynamic checks, allowing me to query the database for any permissions. This is how it originally worked (we later switched it to using the after callback - you can read more about that in this thread).

Documentation

From the get-go, documentation has been extremely important to me. Open source projects live or die by their documentation, so I wanted Bouncer's docs to be the best it possibly can. And especially in the Laravel ecosystem, Taylor has set an extremely high bar for meticulous documentation.

In a way, clear docs are sometimes even more important than the code itself. Without telling your users how to use your tool, very few of them will source dive to figure it out. They'll just move on to the next thing.

I attribute a lot of Bouncer's success to the clear documentation, but there's still a lot more to do on that front. Being the creator and having a clear picture of the whole puzzle, it's easy to forget what people new to the tool struggle with.

For example: as stated earlier, Bouncer is only meant to be used for assigning roles and permissions to users. The actual authorization checks are to be handled like you would in any standard Laravel app. So I figured I don't have to repeat all that, since it's clearly outlined in the Laravel docs. Nevertheless, I still see people struggle with that a lot. They set up their their roles and permissions, then don't know where to go from their. That's an area I still want to flesh out in the docs.

Working up to the release

Let me just state this right now: postponing the 1.0 release till now has done my users a disservice. Bouncer has been stable for years, and actively used in production the world over. And yet, I was always hesitant to release it, since I knew there's so much more I want to add to it. I discussed this at length with Matt on the podcast: I fell down the trap of wanting to make it perfect before releasing it, which is obviously impossible. As Voltaire has warned: "Perfect is the enemy of good".

So as I'm releasing version 1.0 of Bouncer, there are still 2 outstanding features that I really wanted to have included in the initial release, but didn't make the cut:

  1. Per-model roles. For the longest time, people have been clamoring to have a way of assigning roles to users only for a given model (or model class). Here's what that code would look like:

    // Note: this is not implemented yet
    Bouncer::allow('editor')->to(['view', 'edit'])->everything();
    Bouncer::assign('editor')->to($user)->for(Invoice:class);

    With that, the user would be able to do see and edit all invoices, but nothing else. This can of course now be done directly without a role, but doing it through a role would provide another level of flexibility.

    I've tried tackling this many times, but it turns out to be quite tricky, because caching becomes a real nightmare. I still hope to tackle it one day. We'll see.

  2. Ability constraints. Allowing arbitrary constraints on a given ability would add even more granular control:

    // Note: this is not implemented yet
    Bouncer::allow($user)
    ->to('view', Post::class)
    ->where('is_confidential', false);

    If you go source diving in Bouncer, you'll find some code and tests where I started implementing this. It's far from done, but stay tuned.

Overall, Bouncer is in a really great place. Every good product has a long roadmap, and thinking I can get to the end of that road before releasing a 1.0 was both foolish and unrealistic.

So there, live on air during the podcast, I released Bouncer. It felt great!

Enjoy using it!

Well that's a wrap. I hope you try Bouncer in your app, and enjoy using it. Bouncer's API is designed to feel like prose, with every method call reading like a proper English sentence. Try it, and let me know if you get that feeling too!

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate logo

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate

Build your SaaS application in hours. Out-of-the-box multi-tenancy and seamless Stripe integration. Supports subscriptions and one-time purchases, allowing you to focus on building and creating without repetitive setup tasks.

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector
MongoDB logo

MongoDB

Enhance your PHP applications with the powerful integration of MongoDB and Laravel, empowering developers to build applications with ease and efficiency. Support transactional, search, analytics and mobile use cases while using the familiar Eloquent APIs. Discover how MongoDB's flexible, modern database can transform your Laravel applications.

MongoDB

The latest

View all →
Asymmetric Property Visibility in PHP 8.4 image

Asymmetric Property Visibility in PHP 8.4

Read article
Access Laravel Pulse Data as a JSON API image

Access Laravel Pulse Data as a JSON API

Read article
Laravel Forge adds Statamic Integration image

Laravel Forge adds Statamic Integration

Read article
Transform Data into Type-safe DTOs with this PHP Package image

Transform Data into Type-safe DTOs with this PHP Package

Read article
PHPxWorld - The resurgence of PHP meet-ups with Chris Morrell image

PHPxWorld - The resurgence of PHP meet-ups with Chris Morrell

Read article
Herd Executable Support and Pest 3 Mutation Testing in PhpStorm 2024.3 image

Herd Executable Support and Pest 3 Mutation Testing in PhpStorm 2024.3

Read article