Lesson 32 of 50 - Learn how to control what authenticated users are allowed to do in a Laravel application.
Authorization determines what an authenticated user is allowed to access or perform in an application.
Example in a school management system:
Authentication vs Authorization:
| Authentication | Authorization |
|---|---|
| Checks who the user is. | Checks what the user can do. |
| Example: Login with email and password. | Example: Allow only admins to delete students. |
| Commonly uses authentication services. | Uses gates, policies, middleware, or role permissions. |
Authentication and authorization work together to protect application resources.
A gate is a simple authorization rule. Gates are useful when permission logic does not belong to a particular model, such as checking whether a user can access an administration dashboard.
In modern Laravel applications, define gates in
AppServiceProvider.php or an appropriate
authorization provider.
Example:
<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Gate::define('access-admin', function (User $user) {
return $user->role === 'admin';
});
}
}
This example assumes your users table contains a
role column.
Check the permission in a controller:
use Illuminate\Support\Facades\Gate;
if (Gate::allows('access-admin')) {
// User is authorized.
} else {
abort(403, 'Unauthorized action.');
}
A 403 response means the user is not permitted to perform the requested action.
Laravel provides convenient methods for checking authorization permissions.
Check whether a user can access the admin area:
if (auth()->user()->can('access-admin')) {
echo 'Welcome to the admin area';
} else {
echo 'Access denied';
}
Check whether a user cannot access it:
if (auth()->user()->cannot('access-admin')) {
abort(403, 'You cannot access this page.');
}
Use authorization in Blade:
@can('access-admin')
<a href="/admin">Admin Dashboard</a>
@endcan
@cannot('access-admin')
<p>You do not have admin access.</p>
@endcannot
Hiding a link in Blade improves the user interface, but it does not secure the route. Always enforce authorization on the server as well.
A policy organizes authorization rules for a particular model. For example, a StudentPolicy can define who may view, update, or delete student records.
Create a policy:
php artisan make:policy StudentPolicy --model=Student
This creates
app/Policies/StudentPolicy.php.
Example StudentPolicy:
<?php
namespace App\Policies;
use App\Models\Student;
use App\Models\User;
class StudentPolicy
{
public function viewAny(User $user): bool
{
return $user->role === 'admin';
}
public function view(User $user, Student $student): bool
{
return $user->role === 'admin'
|| $user->student_id === $student->id;
}
public function update(User $user, Student $student): bool
{
return $user->role === 'admin';
}
public function delete(User $user, Student $student): bool
{
return $user->role === 'admin';
}
}
This is an example role-based policy. It assumes
the users table has a role field and
a student_id field for linking a
student account to its student record.
Adapt the field names and ownership logic to your database schema. Policies for sensitive records should be tested carefully.
You can authorize actions inside a controller using
the authorize() method.
Example StudentController:
<?php
namespace App\Http\Controllers;
use App\Models\Student;
use Illuminate\Http\Request;
class StudentController extends Controller
{
public function index()
{
$this->authorize('viewAny', Student::class);
$students = Student::paginate(10);
return view('students.index', compact('students'));
}
public function edit(Student $student)
{
$this->authorize('update', $student);
return view('students.edit', compact('student'));
}
public function update(Request $request, Student $student)
{
$this->authorize('update', $student);
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255'],
]);
$student->update($validated);
return redirect()
->route('students.index')
->with('success', 'Student updated successfully.');
}
public function destroy(Student $student)
{
$this->authorize('delete', $student);
$student->delete();
return redirect()
->route('students.index')
->with('success', 'Student deleted successfully.');
}
}
The authorize() method checks the
corresponding policy and throws an authorization
exception when access is denied.
This example assumes the Student model supports mass assignment for the validated fields and that the named route and views exist.
Middleware can protect routes based on authentication or other access requirements.
Protect a route with authentication:
use Illuminate\Support\Facades\Route;
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware('auth');
Authentication middleware ensures the user is logged in. It does not automatically make the user an administrator.
Protect a route using a Gate:
Route::get('/admin', function () {
return view('admin.dashboard');
})->middleware(['auth', 'can:access-admin']);
This route requires both authentication and permission to access the admin area.
For Laravel 11 and later, custom middleware aliases
are typically configured in bootstrap/app.php.
Laravel 10 commonly configures aliases in
app/Http/Kernel.php.
If your application has roles such as admin, teacher, and student, custom middleware can check a user's role.
Create the middleware:
php artisan make:middleware RoleMiddleware
Open app/Http/Middleware/RoleMiddleware.php.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class RoleMiddleware
{
public function handle(
Request $request,
Closure $next,
string $role
): Response {
$user = $request->user();
if (! $user || $user->role !== $role) {
abort(403, 'You are not authorized to access this page.');
}
return $next($request);
}
}
This example assumes the authenticated user has a
role column.
Register the middleware alias:
In Laravel 11 and later, add the alias inside the
existing withMiddleware() configuration
in bootstrap/app.php:
use App\Http\Middleware\RoleMiddleware;
use Illuminate\Foundation\Configuration\Middleware;
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
'role' => RoleMiddleware::class,
]);
})
Merge this alias into your existing configuration
rather than creating a second
withMiddleware() call.
Use the middleware on a route:
Route::get('/admin', function () {
return view('admin.dashboard');
})->middleware(['auth', 'role:admin']);
For Laravel 10, register the alias in the
$middlewareAliases array in
app/Http/Kernel.php.
Policies can also control which actions appear in your Blade views.
Show the Edit button only when authorized:
@can('update', $student)
<a href="{{ route('students.edit', $student->id) }}"
class="btn btn-primary">
Edit Student
</a>
@endcan
Show the Delete button only when authorized:
@can('delete', $student)
<form
action="{{ route('students.destroy', $student->id) }}"
method="POST"
>
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">
Delete Student
</button>
</form>
@endcan
Check authorization in a controller:
if (auth()->user()->cannot('update', $student)) {
abort(403);
}
Blade directives control the interface, while controller authorization checks enforce the permission on the server. Use both when appropriate.
Build an authorization system for a student management application.
Challenge:
Extend the application with three roles: administrator, teacher, and student. Administrators manage all records, teachers manage permitted assignments, and students access only their own profile and assignments.
Never rely only on hiding buttons or menu links. Enforce authorization on every protected server-side operation and verify ownership of individual records.
1. What does authorization determine?
2. Which Laravel feature organizes permissions for a model?
3. Which middleware commonly requires a logged-in user?