Table of Contents
Layered Permission Checks with Context-Aware Authorization
Role-based permissions are great for simple features. But what if you need different access rules based on the state of the data being accessed?
Don’t just check roles. Layer your permission checks with context-aware logic.
The Problem
You have an “Advanced Settings” section. Originally, only super admins could see it:
if (auth()->user()->hasRole('super.admin')) {
// Show advanced settings
}
But now you need more complex rules:
- Tech team should always see it (for debugging)
- Regular admins can see it for draft records
- Only executives can see it for published records
Trying to cram all this into role checks gets messy fast.
The Solution: Layered Authorization
Break authorization into layers:
- Base permission: Does the user have the feature enabled at all?
- Context check: What state is the record in?
- Role check: Does their role allow access for this record state?
- Escape hatch: Always allow admin users (for debugging)
In code:
function canViewAdvancedSettings(User $user, Report $report): bool
{
// Layer 1: Tech team bypass (debugging)
if ($user->hasRole('tech.team')) {
return true;
}
// Layer 2: Base permission check
if (!$user->hasPermission('view.advanced.settings')) {
return false;
}
// Layer 3: Context-aware role check
if ($report->isPublished()) {
// Published reports: only executives
return $user->hasRole('executive');
}
// Default: any user with base permission can view drafts
return true;
}
Why This Works
- Tech team always gets in: They need to debug production issues
- Base permission as gate: Users without the permission never see the feature
- Context-aware rules: Different record states have different access requirements
- Explicit defaults: Clear fallback behavior when special cases don’t apply
Blade Integration
Use this in your blade templates:
@php
$showAdvanced = (function($user, $report) {
if ($user->hasRole('tech.team')) {
return true;
}
if (!$user->hasPermission('view.advanced.settings')) {
return false;
}
if ($report->isPublished()) {
return $user->hasRole('executive');
}
return true;
})(auth()->user(), $report);
@endphp
@if($showAdvanced)
<div class="advanced-settings">
{{-- Advanced settings UI --}}
</div>
@endif
Alternative: Laravel Policies
For reusable logic, extract this to a policy method:
// app/Policies/ReportPolicy.php
public function viewAdvancedSettings(User $user, Report $report): bool
{
if ($user->hasRole('tech.team')) {
return true;
}
if (!$user->hasPermission('view.advanced.settings')) {
return false;
}
if ($report->isPublished()) {
return $user->hasRole('executive');
}
return true;
}
Then use @can in your blade templates:
@can('viewAdvancedSettings', $report)
<div class="advanced-settings">
{{-- Advanced settings UI --}}
</div>
@endcan
Key Insight
Don’t try to solve complex authorization with just roles. Layer your checks:
- Start with escape hatches (tech/admin bypass)
- Check base permissions
- Apply context-aware rules (record state, user attributes, etc.)
- Provide clear defaults
This pattern scales much better than trying to create a role for every combination of access rules.
Leave a Reply