Protect admin routes in Laravel

Today we’ll learn how to protect adminpanel and enhance authorization component of a Laravel application by adding user roles. We will assign each user with a role (e.g. superadmin, admin, member), create an Auth Gate, modify the User model, and utilize the Authenticate middleware to help them get along. Furthermore, we’ll build a skeleton of your future ACL system, which you can adjust and improve according to your needs.

Laravel provides us with a chance to save enormous amount of time on one condition – we should know how to use it. I’m pretty sure anybody who learned Laravel was not only impressed by how simple a complex task can be done, but also by complexity of seemingly simple tasks. The perfect example is authentication – you can create it with a single Artisan command and Laravel will take care of the rest. On the other hand, many beginners struggle with authorization and don’t know how to approach protecting the admin area. Obviously, it’s a trivial task if you have some PHP experience, but doing it the Laravel Way may be tricky.

Getting Started

First, let’s get our authentication up and running. If you already have it set up, you can skip that, otherwise you can simply run following Artisan commands:

php artisan make:auth
php artisan migrate

Laravel generates the authentication controllers and the User model when you create new application, while these commands set everything up so you could start using them. The commands add the HomeController, enable default auth routes, and run the migrations over the database. I’m not going to get into details on this as it’s perfectly detailed in the Laravel docs.

What’s more important is the functionality we don’t get out of the box and that is crucial for any more or less advanced web application – user roles. Luckily, thanks to Laravel, that’s quite trivial to implement ourselves.

User Roles

First, let’s create new migration to add the role field into the database. To do that, we will utilize Artisan:

php artisan make:migration add_user_roles

If you look at the migrations directory (database/migrations/), you’ll see the new migration file has appeared and waits for you to fill it up with the code! It already contains the migration class, so we need to find the method AddUserRoles::up() and ask it politely to create our field:

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('role')->default('member');
    });
}

As you see, we added a new field called role with a default value member. We use a string field, which translates into VARCHAR(255) NOT NULL DEFAULT 'member' if you use MySQL, but you are free to use any other field type, such as SET('member', 'admin', 'superadmin').
Now we need to run the migration so our new field would appear in the database:

php artisan migrate

It’s time to check our database:


— Vincent? Are we happy?

Not so fast, we still need to make our migration rollback when needed. Let’s teach it how to drop the column whenever somebody asks it to:

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('role');
    });
}

Hurray! Our users have roles now, let’s go try them on!

Authorization Gates

Laravel Auth Gates determine whether a user is allowed to perform the action and consist of a closure passed into the Gate facade.

We will create two gates:

  • Gate accessAdminpanel will only allow for superadmins and admins to proceed
  • Gate accessProfile will protect the user account and only allow members to access it

We will place the gates into the AuthServiceProvider::boot() method.
Filename: app/providers/AuthServiceProvider.php

// importing the facade
use Illuminate\Support\Facades\Gate;
...
public function boot()
{
    $this->registerPolicies();

    // the gate checks if the user is an admin or a superadmin
    Gate::define('accessAdminpanel', function($user) {
        return $user->role(['superadmin', 'admin']);
    });

    // the gate checks if the user is a member
    Gate::define('accessProfile', function($user) {
        return $user->role('member');
    });
}

As you see, we added two gates that accept only one parameter – current user. The user is passed into the gate automatically, while the gate isn’t called at all and always returns false if the visitor is not logged in. You can also pass additional parameters into the gate, but we don’t need any at this point.

User Model

Have you noticed anything wrong in the code above? Good catch, the role() method does not exist in the App\User model, we need to create it!

/**
 * checks if the user belongs to a particular group
 * @param string|array $role
 * @return bool
 */
public function role($role) {
    $role = (array)$role;
    return in_array($this->role, $role);
}

This simple method checks if the user role belongs to the list of roles passed as the argument. The $role argument can be either an array, or a string, which will be converted to an array.

Middleware

Middleware, as you might have guessed from the name, is executed in the middle of the request, either before the controller action, or after.

We will utilize the Laravel middleware called Illuminate\Auth\Middleware\Authorize. It provides us with a simple way to call a Gate and check if it allows the user to access something.

Luckily, it’s already enabled, but let’s check that anyway. Open the file app/Http/Kernel.php and find the $routeMiddleware property, it should look like this:

protected $routeMiddleware = [
    'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];

Yay, it’s there and responds to the name can. Let’s see it in action.

Protect a route

First, let’s create a controller to protect using following artisan command (ah, artisan again):

php artisan make:controller Adminpanel/Dashboard

It create new Dashboard controller in the file app/Http/Controllers/Adminpanel/Dashboard.php

However, the controller is empty. We need to create the index action, please open the file and insert following simple method inside the controller:

class Dashboard extends Controller
{
    public function index() {
        return 'This is the dashboard';
    }
}

Now we need to create a route and protect it with the middleware. Open the file routes/web.php and add following code at the end of it:


Route::middleware('can:accessAdminpanel')->group(function() {
    Route::get('/adminpanel/dashboard', 'Adminpanel\Dashboard@index');
    // future adminpanel routes also should belong to the group
});

As you can see, we create a route group that will contain all adminpanel routes. It allows us to apply the middleware once, and it will automatically be applied to all routes the group contains.
Next time somebody opens the URL /adminpanel/dashboard, the router will call the middleware can and pass the parameter accessAdminpanel, which is in fact the name of our Gate.

Let’s Rock!

Finally, it’s time to see everything in action! Register or sign in and open the URL http://your-website.local/adminpanel/dashboard

You should see something like this:

Laravel - Protect Admin Routes

Don’t you worry, that’s exactly what we’ve expected! You are logged in as a regular user, you should not be able to access that route.
If you set APP_DEBUG parameter to false in your .env configuration file, the error message will be brief with no technical information, but it will still forbid the access, which is exactly what we need.

So, we see the Gate denying access to the route, but will it let the superadmin in? Please go ahead and change your role to “superadmin” using PHPMyAdmin or any other tool and open the page once again.

You will see “This is the dashboard”, which means that everything went great and your admin routes are safe and sound!

What’s next?

We completed the basic authorization implementation using Laravel internal features, while learning how to create auth gates and integrate it into your application.

However, the ACL cannot be complete without access permissions, and it might be a topic of one of the next articles. In the meanwhile, you could familiarize yourself with another approach to solving the same problem, explained in the article Laravel 5 – Simple Route Access Control by Joseph Ralph.

Also, you could familiarize yourself with Policies and how they can be utilized to improve the authorization, they are perfectly covered in the Laravel Docs.

A few interesting Laravel posts are coming soon, stay tuned!

8 thoughts on “Protect admin routes in Laravel”

  1. Jason H

    The Route implementation apparently needs some refinement for newer versions of Laravel. Here’s the version I had to use:

    Route::middleware('can:accessAdminpanel'), function() {
    Route::get('/adminpanel/dashboard', 'Adminpanel\Dashboard@index');
    // future adminpanel routes also should belong to the group
    });

  2. mohammad

    this route

    Route::middleware('can:accessAdminpanel')->group(function() {
    Route::get('/adminpanel/dashboard', 'Adminpanel\Dashboard@index');
    // future adminpanel routes also should belong to the group
    });

    is enough to protecting controlles? How to check access level inside controllers?

  3. Vincent

    For Laravel 8 your route should look like this:

    use App\Http\Controllers\Adminpanel\Dashboard;

    Route::middleware(‘can:accessAdminpanel’)->group(function() {
    Route::get(‘/adminpanel/dashboard’, [Dashboard::class, ‘index’]);
    // future adminpanel routes also should belong to the group
    });

Leave a Reply to mohammad Cancel Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.