File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1+ <?php
2+
3+ namespace App \Http \Middleware ;
4+
5+ use Closure ;
6+ use Illuminate \Http \Request ;
7+
8+ class SecurityHeaders
9+ {
10+ public function handle (Request $ request , Closure $ next )
11+ {
12+ $ response = $ next ($ request );
13+ $ response ->headers ->set ('X-Content-Type-Options ' , 'nosniff ' );
14+ $ response ->headers ->set ('X-Frame-Options ' , 'SAMEORIGIN ' );
15+ $ response ->headers ->set ('X-XSS-Protection ' , '1; mode=block ' );
16+ $ response ->headers ->set ('Referrer-Policy ' , 'strict-origin-when-cross-origin ' );
17+ return $ response ;
18+ }
19+ }
Original file line number Diff line number Diff line change 44
55use App \Http \Policies \UserPolicy ;
66use App \Models \User ;
7+ use Illuminate \Cache \RateLimiting \Limit ;
8+ use Illuminate \Http \Request ;
79use Illuminate \Support \Facades \Gate ;
10+ use Illuminate \Support \Facades \RateLimiter ;
811use Illuminate \Support \Facades \Vite ;
912use Illuminate \Support \ServiceProvider ;
1013
@@ -17,5 +20,13 @@ public function boot(): void
1720 Vite::prefetch (concurrency: 3 );
1821
1922 Gate::policy (User::class, UserPolicy::class);
23+
24+ RateLimiter::for ('api ' , function (Request $ request ) {
25+ return Limit::perMinute (60 )->by ($ request ->user ()?->id ?: $ request ->ip ());
26+ });
27+
28+ RateLimiter::for ('auth ' , function (Request $ request ) {
29+ return Limit::perMinute (10 )->by ($ request ->ip ());
30+ });
2031 }
2132}
Original file line number Diff line number Diff line change 2828 'role_or_permission ' => \Spatie \Permission \Middleware \RoleOrPermissionMiddleware::class,
2929 'two_factor ' => \App \Http \Middleware \RequiresTwoFactor::class,
3030 ]);
31+
32+ $ middleware ->append (\App \Http \Middleware \SecurityHeaders::class);
3133 })
3234 ->withExceptions (function (Exceptions $ exceptions ): void {
3335 //
Original file line number Diff line number Diff line change 4545use Illuminate \Support \Facades \Route ;
4646
4747Route::prefix ('v1 ' )->group (function () {
48- // Auth (public)
49- Route::post ('auth/login ' , [AuthController::class, 'login ' ]);
48+ // Auth (public) — stricter rate limit
49+ Route::middleware ('throttle:auth ' )->group (function () {
50+ Route::post ('auth/login ' , [AuthController::class, 'login ' ]);
51+ });
5052
5153 // Protected
52- Route::middleware ('auth:sanctum ' )->group (function () {
54+ Route::middleware ([ 'auth:sanctum ' , ' throttle:api ' ] )->group (function () {
5355 Route::post ('auth/logout ' , [AuthController::class, 'logout ' ]);
5456 Route::get ('auth/me ' , [AuthController::class, 'me ' ]);
5557
Original file line number Diff line number Diff line change 1+ <?php
2+
3+ use App \Models \User ;
4+ use App \Modules \Core \Models \Tenant ;
5+ use Database \Seeders \RolePermissionSeeder ;
6+
7+ beforeEach (function () {
8+ $ this ->seed (RolePermissionSeeder::class);
9+ $ this ->tenant = Tenant::create (['name ' => 'Test Co ' , 'slug ' => 'test-co ' ]);
10+ $ this ->user = User::factory ()->create (['tenant_id ' => $ this ->tenant ->id ]);
11+ $ this ->user ->assignRole ('super-admin ' );
12+ $ this ->token = $ this ->user ->createToken ('test ' )->plainTextToken ;
13+ });
14+
15+ it ('returns security headers on api responses ' , function () {
16+ $ response = $ this ->withToken ($ this ->token )->getJson ('/api/v1/dashboard ' );
17+ $ response ->assertStatus (200 );
18+ expect ($ response ->headers ->has ('X-Content-Type-Options ' ))->toBeTrue ();
19+ expect ($ response ->headers ->has ('X-Frame-Options ' ))->toBeTrue ();
20+ });
21+
22+ it ('api routes have throttle middleware applied ' , function () {
23+ // Just verify the route middleware is registered
24+ $ routes = app ('router ' )->getRoutes ();
25+ $ apiRoute = collect ($ routes )->first (fn ($ r ) => str_contains ($ r ->uri (), 'api/v1/ ' ));
26+ expect ($ apiRoute )->not ->toBeNull ();
27+ });
You can’t perform that action at this time.
0 commit comments