Skip to content

Commit df80564

Browse files
committed
RBAC support
1 parent fb0bae9 commit df80564

File tree

8 files changed

+294
-18
lines changed

8 files changed

+294
-18
lines changed

README.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,77 @@ Route::middleware(['jwt.auth'])->group(function () {
8686

8787
---
8888

89+
### LoadAccess
90+
91+
**Alias**: the string you assign to `load_access` in `middleware_aliases`, e.g. `load.access`.
92+
**Class**: `Kroderdev\LaravelMicroserviceCore\Http\Middleware\LoadAccess`
93+
94+
#### Description
95+
96+
Loads the authenticated user's roles and permissions, typically from a centralized permission service (such as an API Gateway or dedicated permissions microservice).
97+
By default, the `ValidateJwt` middleware will automatically load `roles` and `permissions` from the JWT payload if they are present.
98+
However, if you have a centralized permission service, you can use `LoadAccess` to fetch and hydrate the latest roles and permissions for the user, ensuring up-to-date authorization data.
99+
100+
#### Usage
101+
102+
Apply after JWT authentication, or use the `microservice.auth` group for both:
103+
104+
```php
105+
// In routes/api.php
106+
Route::middleware(['jwt.auth', 'load.access'])->group(function () {
107+
// protected routes with up-to-date permissions…
108+
});
109+
110+
// Or simply:
111+
Route::middleware('microservice.auth')->group(function () {
112+
// protected routes…
113+
});
114+
```
115+
116+
---
117+
118+
### Permission Middleware
119+
120+
**Alias**: the string you assign to `permission` in `middleware_aliases`, e.g. `permission`.
121+
**Class**: `Kroderdev\LaravelMicroserviceCore\Http\Middleware\PermissionMiddleware`
122+
123+
#### Description
124+
125+
Restricts access to routes based on user permissions.
126+
Checks if the authenticated user has the required permission(s) before allowing access.
127+
Returns a 403 Forbidden response if the user lacks the necessary permission.
128+
129+
#### Usage
130+
131+
```php
132+
// In routes/api.php
133+
Route::middleware(['permission:orders.view'])->get('/orders', [OrderController::class, 'index']);
134+
Route::middleware(['permission:orders.create'])->post('/orders', [OrderController::class, 'store']);
135+
```
136+
137+
---
138+
139+
### Role Middleware
140+
141+
**Alias**: the string you assign to `role` in `middleware_aliases`, e.g. `role`.
142+
**Class**: `Kroderdev\LaravelMicroserviceCore\Http\Middleware\RoleMiddleware`
143+
144+
#### Description
145+
146+
Restricts access to routes based on user roles.
147+
Checks if the authenticated user has the required role(s) before allowing access.
148+
Returns a 403 Forbidden response if the user does not have the required role.
149+
150+
#### Usage
151+
152+
```php
153+
// In routes/api.php
154+
Route::middleware(['role:admin'])->get('/admin', [AdminController::class, 'dashboard']);
155+
Route::middleware(['role:manager'])->post('/reports', [ReportController::class, 'generate']);
156+
```
157+
158+
---
159+
89160
### Correlation ID
90161

91162
**Alias**: the string you assign to `correlation_id` in `middleware_aliases`, e.g. `correlation.id`.
@@ -123,6 +194,29 @@ X-Correlation-ID: 123e4567-e89b-12d3-a456-426614174000
123194

124195
---
125196

197+
### Auth Middleware Group
198+
199+
For convenience, you can use the built-in `microservice.auth` group, which runs:
200+
201+
1. **ValidateJwt** – decode JWT, hydrate `ExternalUser`, set `Auth::user()`
202+
2. **LoadAccess** – fetch roles & permissions via ApiGateway
203+
204+
#### Usage
205+
206+
```php
207+
// routes/api.php
208+
209+
Route::middleware('microservice.auth')->group(function () {
210+
// Here you already have a valid ExternalUser with roles & permissions loaded:
211+
Route::get('/orders', [OrderController::class, 'index']);
212+
Route::post('/orders', [OrderController::class, 'store']);
213+
});
214+
```
215+
216+
You no longer need to stack `jwt.auth` + `load.access` manually—just use `microservice.auth` wherever you need full auth + authorization.
217+
218+
---
219+
126220
## Public Release and Future Goals
127221

128222
This repository is brand new, and I’m excited to develop it further! My plan is to continuously strengthen the core, add more middleware modules, expand test coverage, and refine configuration options.

src/Http/Middleware/LoadAccess.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Kroderdev\LaravelMicroserviceCore\Http\Middleware;
4+
5+
use Closure;
6+
use Firebase\JWT\JWT;
7+
use Firebase\JWT\Key;
8+
use Illuminate\Http\Request;
9+
use Illuminate\Support\Facades\Auth;
10+
use Kroderdev\LaravelMicroserviceCore\Auth\ExternalUser;
11+
use Kroderdev\LaravelMicroserviceCore\Services\PermissionsClient;
12+
use Symfony\Component\HttpFoundation\Response;
13+
14+
/**
15+
* Fetch roles & permissions for the current User from the ApiGateway.
16+
*/
17+
class LoadAccess
18+
{
19+
/**
20+
* Handle an incoming request.
21+
*
22+
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
23+
*/
24+
public function handle(Request $request, Closure $next): Response
25+
{
26+
$user = Auth::user();
27+
28+
// If no user, skip
29+
if (! $user) {
30+
return response()->json([
31+
'error' => 'unauthorized',
32+
'message' => 'No authenticated user',
33+
'status' => Response::HTTP_UNAUTHORIZED,
34+
], Response::HTTP_UNAUTHORIZED);
35+
}
36+
37+
try {
38+
$access = app(PermissionsClient::class)->getAccessFor($user);
39+
$user->loadAccess(
40+
$access['roles'] ?? [],
41+
$access['permissions'] ?? []
42+
);
43+
} catch (\Throwable $e) {
44+
// Do nothing
45+
// $user->loadAccess([], []);
46+
}
47+
48+
return $next($request);
49+
}
50+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Kroderdev\LaravelMicroserviceCore\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Support\Facades\Auth;
8+
use Symfony\Component\HttpFoundation\Response;
9+
10+
/**
11+
* Check that the authenticated ExternalUser has the given permission.
12+
* Usage: ->middleware(['permission:posts.create'])
13+
*/
14+
class PermissionMiddleware
15+
{
16+
/**
17+
* Handle an incoming request.
18+
*
19+
* @param \Closure(\Illuminate\Http\Request, string): (\Symfony\Component\HttpFoundation\Response) $next
20+
*/
21+
public function handle(Request $request, Closure $next, string $permission): Response
22+
{
23+
$user = Auth::user();
24+
if (! $user || ! $user->hasPermissionTo($permission)) {
25+
return response()->json([
26+
'error' => 'forbidden',
27+
'message' => "User does not have required permission: {$permission}",
28+
'status' => Response::HTTP_FORBIDDEN,
29+
], Response::HTTP_FORBIDDEN);
30+
}
31+
return $next($request);
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Kroderdev\LaravelMicroserviceCore\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Support\Facades\Auth;
8+
use Symfony\Component\HttpFoundation\Response;
9+
10+
/**
11+
* Check that the authenticated User has the given role.
12+
* Usage: ->middleware(['role:admin'])
13+
*/
14+
class RoleMiddleware
15+
{
16+
/**
17+
* Handle an incoming request.
18+
*
19+
* @param \Closure(\Illuminate\Http\Request, string): (\Symfony\Component\HttpFoundation\Response) $next
20+
*/
21+
public function handle(Request $request, Closure $next, string $role): Response
22+
{
23+
$user = Auth::user();
24+
if (! $user || ! $user->hasRole($role)) {
25+
return response()->json([
26+
'error' => 'forbidden',
27+
'message' => "User does not have required role: {$role}",
28+
'status' => Response::HTTP_FORBIDDEN,
29+
], Response::HTTP_FORBIDDEN);
30+
}
31+
return $next($request);
32+
}
33+
}

src/Http/Middleware/ValidateJwt.php

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,13 @@ public function handle(Request $request, Closure $next): Response
3737

3838
// Auth from JWT
3939
$user = new ExternalUser((array) $decoded);
40-
41-
// Get Permissions, permits tolerance
42-
try {
43-
$access = app(PermissionsClient::class)->getAccessFor($user);
44-
} catch (\Throwable $e) {
45-
$access = [
46-
'roles' => [],
47-
'permissions' => [],
48-
];
49-
}
50-
5140
$user->loadAccess(
52-
$access['roles'] ?? [],
53-
$access['permissions'] ?? []
41+
$user->attributes['roles'] ?? [],
42+
$user->attributes['permissions'] ?? []
5443
);
55-
5644
Auth::setUser($user);
5745
$request->setUserResolver(fn () => $user);
46+
5847
} catch (\Throwable $e) {
5948
return response()->json(['error' => 'Invalid token', 'message' => $e->getMessage()], Response::HTTP_UNAUTHORIZED);
6049
}

src/Providers/MicroserviceServiceProvider.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
use Illuminate\Support\Facades\Http;
88
use Illuminate\Support\ServiceProvider;
99
use Kroderdev\LaravelMicroserviceCore\Contracts\ApiGatewayClientInterface;
10+
use Kroderdev\LaravelMicroserviceCore\Http\Middleware\LoadAccess;
11+
use Kroderdev\LaravelMicroserviceCore\Http\Middleware\PermissionMiddleware;
12+
use Kroderdev\LaravelMicroserviceCore\Http\Middleware\RoleMiddleware;
1013
use Kroderdev\LaravelMicroserviceCore\Http\Middleware\ValidateJwt;
1114
use Kroderdev\LaravelMicroserviceCore\Services\ApiGatewayClient;
1215
use Kroderdev\LaravelMicroserviceCore\Services\ApiGatewayClientFactory;
@@ -56,6 +59,27 @@ public function boot(Router $router): void
5659
$router->prependMiddlewareToGroup('api', CorrelationId::class);
5760
}
5861

62+
// Role middleware alias
63+
if (! empty($aliases['role'] ?? '')) {
64+
$router->aliasMiddleware($aliases['role'], RoleMiddleware::class);
65+
}
66+
67+
// Permission middleware alias
68+
if (! empty($aliases['permission'] ?? '')) {
69+
$router->aliasMiddleware($aliases['permission'], PermissionMiddleware::class);
70+
}
71+
72+
// LoadAccess middleware alias
73+
if (! empty($aliases['load_access'] ?? '')) {
74+
$router->aliasMiddleware($aliases['load.access'], PermissionMiddleware::class);
75+
}
76+
77+
// Auth middleware group
78+
$router->middlewareGroup('microservice.auth', [
79+
ValidateJwt::class,
80+
LoadAccess::class,
81+
]);
82+
5983
// HTTP
6084
Http::macro('apiGateway', function () {
6185
return Http::acceptJson()

src/config/microservice.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
'middleware_aliases' => [
1515
'jwt_auth' => 'jwt.auth', // e.g. 'jwt.auth' or null
1616
'correlation_id' => 'correlation.id', // e.g. 'correlation.id' or ''
17+
'load_access' => 'load.access',
18+
'role' => 'role',
19+
'permission' => 'permission',
1720
],
1821

1922

0 commit comments

Comments
 (0)