Laravel - Authorization and Middlewares
Limit Access to Authorized Users.
https://laracasts.com/series/laravel-6-from-scratch/episodes/50
Authorize in Blade Views
How can I conditionally display something in a blade view?
@can('update-post', $post)
<a class="btn btn-primary float-right" href="">Edit</a>
@endcan
How we define update-post ?
I have to define a Gate in the AuthServiceProvider.
- In the gate I can check conditions and return true if the user is authorized.
- The following code is true when we have an authenticated users, otherwise is false (for guests)
- In this case I pass to the callback the $post.
app/Providers/AuthServiceProvider.php
public function boot()
{
$this->registerPolicies();
Passport::routes();
Gate::define('update-post', function(User $user, Post $post){
return true;
});
}
In case we need to authorise also guests, we need to make $user optional.
- In this way it’s not mandatory. So also guests can see that.
Gate::define('update-post', function(?User $user, Post $post){
So if I have a belongsTo relationship defined in the post model I can do:
Gate::define('update-post', function(User $user, Post $post){
return $post->user->is($user); //true if the authenticated $user is the post creator ($post->user)
});
Authorise in Controllers methods
First way
In the previous example we authorized the display of a button in a blade view, but this doesn’t prevent anybody to submit a push request!
https://laravel.com/docs/8.x/authorization#via-middleware
- Laravel provides a helpful authorize method to any of your controllers which extend the App\Http\Controllers\Controller base class.
- Like the can method we use for views, this method accepts the name of the action you wish to authorize and the relevant model.
- If the action is not authorized, the authorize method will throw an Illuminate\Auth\Access\AuthorizationException,
- which the default Laravel exception handler will convert to an HTTP response with a 403 status code:$this->authorize
So, in our controller methods I can use it like this:
public function update(Request $request, Post $post)
{
$this->authorize(‘update-post', $post); // Check if user has permissions to update-post
}
Or:
public function store(Request $request)
{
$this->authorize(‘create-post', Post::class); // Check if user has permissions to create-post
}
What can we do to void to store all the Authorization logic in the AuthServiceProvider?
BEST WAY!
- app/Providers/AuthServiceProvider.php
- Since it get a mess soon!
- Better to use dedicate policy classes!!
Create for each model the relative policy!
Check the documentation:
php artisan
php artisan help make:policy (create a new policy class)
So I do:
php artisan make:policy PostPolicy --model=Post
- We can see it as a class that encapsulate an authorisation policy for a model
This creates: app/Policies/PostPolicy.php
public function create(User $user)
{
return true; // True for any user that is authenticated.
}
public function update(User $user, Post $post)
{
return $post->user->is($user); // True if the authenticated $user is the post creator ($post->user).
}
Here it generates automatically all the policies for any possible standard case!!
- We can then call any method we want here.
- Or delete the one that are automatically created.
- Or rename the one automatically created.
- What if I need only one method, because in my controller I have just one method?
- I can delete all the rest.
- Do we have permission to work with this model.
So I can return to my service provider and remove the two Gate I have created. app/Providers/AuthServiceProvider.php
Gate::define('update-post', function(User $user, Post $post){
return $post->user->is($user); //true if the authenticated $user is the post creator ($post->user)
});
Gate::define('create-post', function(User $user){
return true; // True if the user is authenticated
});
And, we update also the controller with the names that have been created in the PostPolicy.php So from:
$this->authorize('create-post', Post::class);
$this->authorize(‘update-post', Post::class);
To:
$this->authorize('create', Post::class);
$this->authorize(‘update', Post::class);
And also I can change the name of the authorisation in the @can directive I used in the blade view!!
How to manage user roles?
- Eg. Administrator, manager etc..
- And check Eg. isAdmin()
Laravel with the authorisation plugin doesn’t provide roles out of the box when we install.
I can use a spatie package for that: https://spatie.be/docs/laravel-permission/v3/introduction
composer require spatie/laravel-permission
With this package I can assign roles to users.
$user->assignRole('Admin');
I can add to the user model a function to check if the user is an admin or super admin.
app/Models/User.php
/**
* Return true if the user is an administrator
*
* @return bool
*/
public function isAdmin(): bool
{
return $this->hasRole(['Super Admin', 'Admin']);
}
Now: In the CONTROLLERS I can check like this:
Auth::user()->isAdmin()
Then in your BLADE VIEWS I can call it:
@if(Auth::user()->isAdmin())
enter code here
@endif
Now I can allow the administrators to do whatever action to the posts?
- I can add to the Post policy a method before()
- This fires before any other policy method
app/Policies/PostPolicy.php
...
/**
* Give the admin full authorization to this resource
*
* @param \App\Models\User $user
* @return mixed
*/
public function before(User $user)
{
if($user->isAdmin()){
return true;
}
}
...
Or even better!
- We can give to the admins full access to any resource of the website
- So we handle globally defining it in the AuthServiceProvider.php
app/Providers/AuthServiceProvider.php
public function boot()
{
$this->registerPolicies();
// Give the admin full authorization to this resource
Gate::before(function (User $user){
if($user->isAdmin()){
return true;
}
});
}
Authentication at the Route level
https://laracasts.com/series/laravel-6-from-scratch/episodes/53?autoplay=true
We can also authenticate at the route level using a middleware.
Route::get(‘/reports’, function (){
Return ’the secret reports’;
})->middleware(‘can:view_reports');