Solution commentée
// app/Http/Middleware/CheckRole.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckRole
{
public function handle(Request $request, Closure $next, string ...$roles): Response
{
if (!$request->user()) {
return $request->expectsJson()
? response()->json(['message' => 'Non authentifié.'], 401)
: redirect()->route('login');
}
if (!$request->user()->hasRole(...$roles)) {
return $request->expectsJson()
? response()->json(['message' => 'Accès refusé.'], 403)
: abort(403);
}
return $next($request);
}
}
// bootstrap/app.php — Laravel 11
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'role' => \App\Http\Middleware\CheckRole::class,
]);
})
// app/Policies/ArticlePolicy.php
namespace App\Policies;
use App\Models\Article;
use App\Models\User;
class ArticlePolicy
{
// Admins passent partout
public function before(User $user): ?bool {
return $user->isAdmin() ? true : null;
}
public function viewAny(?User $user): bool { return true; }
public function view(?User $user, Article $article): bool {
return $article->isPublished() || $user?->id === $article->author_id;
}
public function create(User $user): bool {
return $user->hasRole('author', 'editor', 'admin');
}
public function update(User $user, Article $article): bool {
return $user->id === $article->author_id || $user->hasRole('editor');
}
public function delete(User $user, Article $article): bool {
return $user->id === $article->author_id && $user->hasRole('author', 'editor');
}
}
// app/Models/User.php — méthodes helper
public function isAdmin(): bool { return $this->role === 'admin'; }
public function isEditor(): bool { return in_array($this->role, ['editor', 'admin']); }
public function hasRole(string ...$roles): bool {
return in_array($this->role, $roles);
}
// app/Http/Controllers/Api/AuthController.php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class AuthController extends Controller
{
public function register(Request $request): JsonResponse {
$data = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
]);
$user = User::create([...$data, 'password' => bcrypt($data['password'])]);
$token = $user->createToken('api-token', ['read']);
return response()->json([
'token' => $token->plainTextToken,
'token_type' => 'Bearer',
], 201);
}
public function login(Request $request): JsonResponse {
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (!auth()->attempt($credentials)) {
return response()->json(['message' => 'Identifiants incorrects.'], 401);
}
$user = auth()->user();
$abilities = $user->hasRole('author', 'editor', 'admin') ? ['read', 'write'] : ['read'];
$token = $user->createToken('api-token', $abilities);
return response()->json([
'token' => $token->plainTextToken,
'token_type' => 'Bearer',
'user' => ['id' => $user->id, 'name' => $user->name, 'role' => $user->role],
]);
}
public function logout(Request $request): JsonResponse {
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Déconnecté.']);
}
public function me(Request $request): JsonResponse {
return response()->json(['data' => $request->user()]);
}
}
// routes/api.php
Route::prefix('v1')->group(function () {
Route::post('/auth/register', [AuthController::class, 'register']);
Route::post('/auth/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::get('/auth/me', [AuthController::class, 'me']);
Route::post('/auth/logout', [AuthController::class, 'logout']);
Route::middleware('ability:read')->group(function () {
Route::apiResource('articles', ArticleController::class)->only(['index', 'show']);
});
Route::middleware('ability:write')->group(function () {
Route::apiResource('articles', ArticleController::class)->except(['index', 'show']);
});
});
});