Laravel 5.8 REST API [Passport for authentication] [Laravel-permission by Spatie for Role/Permission]

Manash Chakrobortty
7 min readJul 12, 2019

Developing application for multiple platform (web & mobile) with single source of back end, require APIs or web services. Keeping APIs call safe and authenticated is important to protect the data being transferred between application to application. We are going to use Laravel Passport which allows us to authenticate APIs. For role/permission we will use Laravel-Permission.

Initial Setup:
Our final implementation will be a simple blog. First we will create new laravel project

laravel new blog

In blog we will have 2 types of user:
(1) Admin
(2) Blogger

Authentication

Install Passport: In command line go to the blog directory

composer require laravel/passport

Run migration for passport tables

php artisan migrate

Final command to create the encryption keys which are necessary to generate secure access tokens.

php artisan passport:install

Now we need to do some manual configuration in the code level.

app/user.php

<?php

namespace App;

use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
use HasApiTokens, Notifiable;
}

app/Providers/AuthServiceProvider.php

<?php

namespace App\Providers;

use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];

/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();

Passport::routes();
}
}

config/auth.php

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],

'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],

Routing
We can implement middleware in route section or in the controller. We will use both. Since we are creating REST API, so we need to add route in the routes/api.php file

Route::post('login', 'API\LoginController@login');Route::middleware('auth:api')->group( function () {
Route::resource('users', 'API\UserController');
});

Controller

LoginController.php

<?phpnamespace App\Http\Controllers\API;use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
public $successStatus = 200;
/**
* login api
*
* @return \Illuminate\Http\Response
*/
public function login(){
if(Auth::attempt(['email' => request('email'), 'password' => request('password')])){
$user = Auth::user();
$success['token'] = $user->createToken('MyApp')-> accessToken;
return response()->json(['success' => $success], $this-> successStatus);
}
else{
return response()->json(['error'=>'Unauthenticated'], 401);
}
}
}

UserController.php

<?phpnamespace App\Http\Controllers\API;use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
public $successStatus = 200;
public function __construct(){
$this->middleware(['isAdmin'])->only('index');
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$users = User::all();
return response()->json(['users' => $users], $this-> successStatus);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|email',
'password' => 'required',
'c_password' => 'required|same:password',
]);
if ($validator->fails()) {
return response()->json(['error'=>$validator->errors()], 401);
}
$input = $request->all();
$input['password'] = bcrypt($input['password']);
$user = User::create($input);
$success['token'] = $user->createToken('MyApp')-> accessToken;
$success['name'] = $user->name;
return response()->json(['success'=>$success], $this-> successStatus);
}
/**
* Display the specified resource.
*
* @param \App\User $user
* @return \Illuminate\Http\Response
*/
public function show(User $user)
{
return response()->json(['user'=>$user], $this-> successStatus);
}
}

Testing

We already have admin user (Created by db seeding or manually), who can create new user

Login

User List: After logged in we will get a success token, which will require it to make authentic request. Now header should look like below:

'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$token,
]

Role-Permission:

Roles and permissions are an important part of maximum application. You can create your own solution for role/permission. But there is a package, which provide API to deal with roles and permissions more easily. Also, the final code is more reader-friendly and easier to understand. Lets start,

To install the laravel-permission package run

composer require spatie/laravel-permission

Next include the package in the service providers in config/app.php

'providers' => [
...
Spatie\Permission\PermissionServiceProvider::class,
];

Now publish the migration file for this package

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"

Publish the configuration file for this package by running

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"

That’s all for package configuration. Now we are going to use it. Firstly we create a separate controller to manage Role/Permission. Here it is

PermissionController.php

<?phpnamespace App\Http\Controllers\API;use Illuminate\Http\Request; 
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Support\Facades\Auth;
use Validator;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class PermissionController extends Controller {
public $successStatus = 200;
public function __construct(){
$this->middleware(['isAdmin']);
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function role_list()
{
$roles = Role::all();
return response()->json(['roles' => $roles], $this-> successStatus);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function role_store(Request $request){
$validator = Validator::make($request->all(), [
'name' => 'required|unique:roles'
]);
if($validator->fails()){
return response()->json(['error'=>$validator->errors()], 401);
}
$input = $request->all();
$role = Role::create(['name' => $input['name']]);
if($role){
return response()->json(['role' => $role], 200);
}
return response()->json(['error' => "Unable to create a role"], 401);
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function permission_list()
{
$permissions = Permission::all();
return response()->json(['permissions' => $permissions], $this-> successStatus);
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function permission_store(Request $request){
$validator = Validator::make($request->all(), [
'name' => 'required'
]);
if($validator->fails()){
return response()->json(['error' => $validator->errors()], 401);
}
$input = $request->all();
$permission = Permission::create(['name' => $input['name']]);
if($permission){
return response()->json(['permission' => $permission], $this-> successStatus);
}
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function role_has_permissions(Request $request, Role $role){
$validator = Validator::make($request->all(), [
'permission_id' => 'required|exists:permissions,id'
]);
if($validator->fails()){
return response()->json(['error' => $validator->errors()], 401);
}
$permission = Permission:: find($request['permission_id'])->firstOrFail();
if($role->givePermissionTo($permission)){
return response()->json(['success' => $role], $this-> successStatus);
}
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function assign_user_to_role(Request $request, Role $role){
$validator = Validator::make($request->all(), [
'user_id' => 'required|exists:users,id'
]);
if($validator->fails()){
return response()->json(['error' => $validator->errors()], 401);
}
$user = User:: find($request['user_id'])->firstOrFail();
if($user->assignRole($role)){
return response()->json(['success' => $user], $this-> successStatus);
}
}
}

For simplicity we will just create a single role named “Admin”. Assign one or multiple user to that role. Considering other users as normal user. Admin will have all access & modification permission. But the normal user will have only access in their content. To make it possible we need to check user role & permission before proceeding any action. Laravel-permission package provides necessary API. Using below we can check role of user.

$user->hasRole('writer');

The convenient way is creating a middle-ware to check role & permission using above package API. Lets see how is the middle-ware look like.

Middle-ware: IsAdmin.php

<?phpnamespace App\Http\Middleware;use Closure;
use Illuminate\Support\Facades\Auth;
use App\User;
class IsAdmin
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if(!Auth::user()->hasRole('Admin')){
return response()->json(['error'=>'Unauthorised'], 401);
}
return $next($request);
}
}

Middle-ware: ResourceModification.php

<?phpnamespace App\Http\Middleware;use Closure;
use Illuminate\Support\Facades\Auth;
use App\User;
class ResourceModification
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$param = $request->route()->parameters();

$loggedin_user = Auth::user();
if(!$loggedin_user->hasRole('Admin') && $loggedin_user->id != $param['post']['user_id']){
return response()->json(['error'=>'Unauthorised'], 401);
}
return $next($request);
}
}

Registering middle-ware in app/Http/Kernel.php file:

protected $routeMiddleware = [
...,
'isAdmin' => \App\Http\Middleware\IsAdmin::class,
'resourceModification' => \App\Http\Middleware\ResourceModification::class,
];

Using those middle-ware we can restrict user from using unauthorized functionalities. Implement middle-ware in the controller.

public function __construct(){
$this->middleware(['auth:api'])->except('index', 'show');
$this->middleware(['resourceModification'])->only('update', 'destroy');;
}

Also we have used Route::fallback() to help customize 404. Finally our route file look like:

Route::post('login', 'API\LoginController@login');Route::middleware('auth:api')->group( function () {
Route::resource('users', 'API\UserController');
Route::get('roles', 'API\PermissionController@role_list');
Route::post('roles', 'API\PermissionController@role_store');
Route::get('permissions', 'API\PermissionController@permission_list');
Route::post('permissions', 'API\PermissionController@permission_store');
Route::post('rolepermissions/{role}', 'API\PermissionController@role_has_permissions');
Route::post('assignuserrole/{role}', 'API\PermissionController@assign_user_to_role');
});
Route::resource('posts', 'API\PostController');
Route::fallback(function(){
return response()->json(['message' => 'Not Found.'], 404);
})->name('api.fallback.404');

Customizing ModelNotFoundException Responses in app/Exceptions/Handler.php file

use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Route;
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException && $request->isJson()) {
return Route::respondWithRoute('api.fallback.404');
}
return parent::render($request, $exception);
}

You can find the full source code here:
https://github.com/manashcse11/laravel-rest-passport

--

--