How to implement an access level system in Laravel (Part 1)
This is my very first article on medium and I’m so excited
hope to enjoy it
Let’s assume that we want to give limited access to admins of your system,
for example, some of the admins only can see the users but some of them can only delete the users
Let’s jump right into it
create a fresh laravel project
laravel new blog
Set your database connection in .env file and run the migrations
php artisan migrate && php artisan db:seed
In this project, we have User, Article models (as simple as possible)
php artisan make:model Article -a
Now create a simple CRUD for Article
<?php
namespace App\Http\Controllers;
use App\Models\Article;
use Illuminate\Http\Request;
class ArticleController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response|string
*/
public function index()
{
return "get article list";
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response|string
*/
public function create()
{
return "create new article";
}
/**
* Display the specified resource.
*
* @param \App\Models\Article $article
* @return \Illuminate\Http\Response|string
*/
public function show(Article $article)
{
return "shw one article";
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Article $article
* @return \Illuminate\Http\Response|string
*/
public function update(Request $request, Article $article)
{
return "edit article";
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Article $article
* @return \Illuminate\Http\Response|string
*/
public function destroy(Article $article)
{
return "delete article";
}
}
Now we must implement an Authentication system
in this project I used JWT
To install JWT package just run command:
composer require tymon/jwt-auth
And you need to do some changes in your User Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use HasFactory, Notifiable;
// Rest omitted for brevity
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
Let’s make some APIs for login and logout and get the logged-in user
The AuthController should look like this :
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class AuthController extends Controller
{
/**
* Create a new AuthController instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth:api', ['except' => ['login']]);
}
/**
* Get a JWT via given credentials.
*
* @return \Illuminate\Http\JsonResponse
*/
public function login()
{
$credentials = request(['email', 'password']);
if (! $token = auth()->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
/**
* Get the authenticated User.
*
* @return \Illuminate\Http\JsonResponse
*/
public function me()
{
return response()->json(auth()->user());
}
/**
* Get the token array structure.
*
* @param string $token
*
* @return \Illuminate\Http\JsonResponse
*/
protected function respondWithToken(string $token): \Illuminate\Http\JsonResponse
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth()->factory()->getTTL() * 60
]);
}
}
So let’s define APIs endpoint in routes/api.php
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::group([
'prefix' => 'auth'
], function () {
Route::post('login', [\App\Http\Controllers\AuthController::class , 'login']);
Route::post('me', [\App\Http\Controllers\AuthController::class , 'me']);
});
Now you can log in and receive the token
Spatie package
In this project, I used the Laravel-permission package
Installation
composer require spatie/laravel-permission
You should publish the migration and the config/permission.php
config file with:
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
Run the migrations: After the config and migration have been published and configured, you can create the tables for this package by running:
php artisan migrate
In Your User Model add Spatie\Permission\Traits\HasRoles trait
use Spatie\Permission\Traits\HasRoles;class User extends Authenticatable implements JWTSubject
{
use HasFactory, Notifiable , HasRoles;
protected $guard_name = 'api';
....
After this, we should create a controller for access levels
php artisan make:controller AccessLevelController
In your system, you may have many roles such as admin, super admin, accountant, …
because of that, you should have a method to add new roles
In AccessLevelController class add “createRole” method
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;
class AccessLevelController extends Controller
{
public function createRole(Request $request): \Illuminate\Http\JsonResponse
{
$role = Role::create(['name' => $request->name]);
return response()->json($role);
}
}
But we have constant permissions like “View Articles”, “show Articles”, “Create Articles”, “Update Articles”, “Delete Articles”
Byword constant I mean we must have a policy that cannot be changed
to make this happen make a PHP file called access_level.php in /config
directory
<?php
return [
'Articles' => [
'View Articles',
'Show Articles',
'Update Articles',
'Create Articles',
'Delete Articles'
]
];
Add a method to create permissions in the database
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
class AccessLevelController extends Controller
{
public function createRole(Request $request): \Illuminate\Http\JsonResponse
{
$role = Role::create(['name' => $request->name]);
return response()->json($role);
}
public function createPermission()
{
DB::beginTransaction();
try {
$config = config('access_level');
$permissions = [];
foreach ($config as $permission) {
foreach ($permission as $item) {
$permissions[] = $item;
}
}
foreach ($permissions as $permission_name)
{
if (DB::table('permissions')->where('name' , $permission_name)->count() == 0)
{
Permission::query()->create(['name' => $permission_name]);
}
}
DB::commit();
return 'done';
} catch (\Exception $exception) {
DB::rollBack();
return response([
'error' => $exception->getMessage()
]);
}
}
}
Now it is time to assign permissions to a role
add another method to AccessLevelController called assignPermission
public function assignPermission(Role $role, Request $request)
{
$this->validate($request , [
'permission_ids' => 'required|array',
'permission_ids.*' => 'required|exists:permissions,id'
]);
$permissions = Permission::query()->whereIn('id', $request->get('permission_ids'))->get();
$role->syncPermissions($permissions);
return response('permissions were assigned to ' . $role->name . ' role successfully');
}
Finally, give the role to users with “giveRoleToUsers” method
public function giveRoleToUsers(Role $role, Request $request)
{
$this->validate($request, [
'user_ids' => 'required|array',
'user_ids.*' => 'required|exists:users,id',
]);
User::query()->whereIn('id', $request->get('user_ids'))->get()->each(function ($user) use ($role) {
$user->assignRole($role->name);
});
return response($role->name . ' role was assigned to users');
}
create some role and permission then give the role to a user (via postman)