diff --git a/SANCTUM_SETUP_GUIDE.html b/SANCTUM_SETUP_GUIDE.html new file mode 100644 index 0000000..3400ff7 --- /dev/null +++ b/SANCTUM_SETUP_GUIDE.html @@ -0,0 +1,752 @@ + + SANCTUM_SETUP_GUIDE + + + + + + + + + + + + + +
+ +

Руководство по установке и настройке Laravel Sanctum с API авторизацией

+

Описание

+

Данное руководство описывает процесс установки и настройки Laravel Sanctum для API аутентификации с использованием wadakatu/laravel-spectrum для автоматической генерации Swagger документации.

+
+

Содержание

+
    +
  1. Установка пакетов
  2. +
  3. Настройка Sanctum
  4. +
  5. Структура API
  6. +
  7. API Endpoints
  8. +
  9. Использование API
  10. +
  11. Swagger документация
  12. +
  13. Команды Artisan
  14. +
+
+

1. Установка пакетов

+

1.1 Установка Laravel Sanctum

+
composer require laravel/sanctum
+

1.2 Установка wadakatu/laravel-spectrum

+
composer require wadakatu/laravel-spectrum
+

1.3 Публикация конфигураций

+
# Публикация Sanctum
+php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
+
+# Публикация Spectrum (если требуется)
+php artisan vendor:publish --provider="Wadakatu\LaravelSpectrum\LaravelSpectrumServiceProvider"
+

1.4 Запуск миграций

+
php artisan migrate
+
+

Примечание: Если таблица personal_access_tokens уже существует, пропустите этот шаг.

+
+
+

2. Настройка Sanctum

+

2.1 Обновление модели User

+

Добавьте трейт HasApiTokens в модель User:

+
// app/Models/User.php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Foundation\Auth\User as Authenticatable;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Sanctum\HasApiTokens;
+
+class User extends Authenticatable
+{
+    use HasApiTokens, HasFactory, Notifiable;
+
+    // ...
+}
+

2.2 Настройка middleware

+

Обновите файл bootstrap/app.php:

+
// bootstrap/app.php
+
+return Application::configure(basePath: dirname(__DIR__))
+    ->withRouting(
+        web: __DIR__.'/../routes/web.php',
+        api: __DIR__.'/../routes/api.php',
+        commands: __DIR__.'/../routes/console.php',
+        health: '/up',
+    )
+    ->withMiddleware(function (Middleware $middleware): void {
+        //
+    })
+    ->withExceptions(function (Exceptions $exceptions): void {
+        //
+    })->create();
+
+

Важно: Не используйте statefulApi() для чистого token-based API, это вызовет ошибку 419 CSRF.

+
+

2.3 Конфигурация Sanctum

+
// config/sanctum.php
+
+return [
+    'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
+        '%s%s',
+        'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
+        env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
+    ))),
+
+    'guard' => ['web'],
+
+    'expiration' => null,
+
+    'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
+
+    'middleware' => [
+        'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
+        'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
+    ],
+];
+

+

3. Структура API

+

3.1 Директории

+
app/
+├── Http/
+│   ├── Controllers/
+│   │   └── Api/
+│   │       ├── AuthController.php
+│   │       └── PostController.php
+│   ├── Requests/
+│   │   ├── LoginRequest.php
+│   │   ├── RegisterRequest.php
+│   │   ├── StorePostRequest.php
+│   │   └── UpdatePostRequest.php
+│   └── Resources/
+│       ├── PostCollection.php
+│       ├── PostResource.php
+│       └── UserResource.php
+└── Policies/
+    └── PostPolicy.php
+

3.2 API Resources

+

Laravel Spectrum автоматически определяет структуру ответа из API Resources.

+
// app/Http/Resources/UserResource.php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class UserResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'name' => $this->name,
+            'email' => $this->email,
+            'role' => $this->role,
+            'created_at' => $this->created_at,
+        ];
+    }
+}
+
// app/Http/Resources/PostResource.php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class PostResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'title' => $this->title,
+            'content' => $this->content,
+            'user_id' => $this->user_id,
+            'user' => new UserResource($this->whenLoaded('user')),
+            'created_at' => $this->created_at,
+            'updated_at' => $this->updated_at,
+        ];
+    }
+}
+

3.3 API Controllers

+

Laravel Spectrum автоматически анализирует контроллеры для генерации документации.

+

AuthController

+
// app/Http/Controllers/Api/AuthController.php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\LoginRequest;
+use App\Http\Requests\RegisterRequest;
+use App\Http\Resources\UserResource;
+use App\Models\User;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Hash;
+
+class AuthController extends Controller
+{
+    /**
+     * Register a new user.
+     */
+    public function register(RegisterRequest $request): JsonResponse
+    {
+        $user = User::create([
+            'name' => $request->name,
+            'email' => $request->email,
+            'password' => Hash::make($request->password),
+        ]);
+
+        $token = $user->createToken('auth-token')->plainTextToken;
+
+        return response()->json([
+            'message' => 'User registered successfully',
+            'user' => new UserResource($user),
+            'token' => $token,
+        ], 201);
+    }
+
+    /**
+     * Login user.
+     */
+    public function login(LoginRequest $request): JsonResponse
+    {
+        $user = User::where('email', $request->email)->first();
+
+        if (!$user || !Hash::check($request->password, $user->password)) {
+            return response()->json([
+                'message' => 'Invalid credentials',
+            ], 401);
+        }
+
+        $token = $user->createToken('auth-token')->plainTextToken;
+
+        return response()->json([
+            'message' => 'Login successful',
+            'user' => new UserResource($user),
+            'token' => $token,
+        ]);
+    }
+
+    /**
+     * Logout user.
+     */
+    public function logout(Request $request): JsonResponse
+    {
+        $request->user()->currentAccessToken()->delete();
+
+        return response()->json([
+            'message' => 'Logged out successfully',
+        ]);
+    }
+
+    /**
+     * Get authenticated user.
+     */
+    public function user(Request $request): JsonResponse
+    {
+        return response()->json([
+            'user' => new UserResource($request->user()),
+        ]);
+    }
+}
+

PostController

+
// app/Http/Controllers/Api/PostController.php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\StorePostRequest;
+use App\Http\Requests\UpdatePostRequest;
+use App\Http\Resources\PostCollection;
+use App\Http\Resources\PostResource;
+use App\Models\Post;
+use Illuminate\Http\JsonResponse;
+
+class PostController extends Controller
+{
+    /**
+     * Display a listing of posts.
+     */
+    public function index(): PostCollection
+    {
+        $posts = Post::with('user')->latest()->paginate(15);
+        return new PostCollection($posts);
+    }
+
+    /**
+     * Store a newly created post.
+     */
+    public function store(StorePostRequest $request): JsonResponse
+    {
+        $post = Post::create([
+            'title' => $request->title,
+            'content' => $request->content,
+            'user_id' => $request->user()->id,
+        ]);
+
+        $post->load('user');
+
+        return response()->json([
+            'message' => 'Post created successfully',
+            'post' => new PostResource($post),
+        ], 201);
+    }
+
+    /**
+     * Display the specified post.
+     */
+    public function show(Post $post): JsonResponse
+    {
+        $post->load('user');
+        return response()->json([
+            'post' => new PostResource($post),
+        ]);
+    }
+
+    /**
+     * Update the specified post.
+     */
+    public function update(UpdatePostRequest $request, Post $post): JsonResponse
+    {
+        $this->authorize('update', $post);
+
+        $post->update($request->validated());
+
+        return response()->json([
+            'message' => 'Post updated successfully',
+            'post' => new PostResource($post),
+        ]);
+    }
+
+    /**
+     * Remove the specified post.
+     */
+    public function destroy(Post $post): JsonResponse
+    {
+        $this->authorize('delete', $post);
+
+        $post->delete();
+
+        return response()->json([
+            'message' => 'Post deleted successfully',
+        ]);
+    }
+}
+

3.4 Form Requests

+

Laravel Spectrum автоматически определяет правила валидации из Form Requests.

+
// app/Http/Requests/RegisterRequest.php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class RegisterRequest extends FormRequest
+{
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    public function rules(): array
+    {
+        return [
+            'name' => ['required', 'string', 'max:255'],
+            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
+            'password' => ['required', 'string', 'min:8', 'confirmed'],
+        ];
+    }
+}
+
// app/Http/Requests/LoginRequest.php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class LoginRequest extends FormRequest
+{
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    public function rules(): array
+    {
+        return [
+            'email' => ['required', 'string', 'email'],
+            'password' => ['required', 'string'],
+        ];
+    }
+}
+
// app/Http/Requests/StorePostRequest.php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class StorePostRequest extends FormRequest
+{
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    public function rules(): array
+    {
+        return [
+            'title' => ['required', 'string', 'max:255'],
+            'content' => ['required', 'string'],
+        ];
+    }
+}
+

+

4. API Endpoints

+

4.1 Маршруты

+
// routes/api.php
+
+use App\Http\Controllers\Api\AuthController;
+use App\Http\Controllers\Api\PostController;
+use Illuminate\Support\Facades\Route;
+
+/*
+|--------------------------------------------------------------------------
+| API Routes
+|--------------------------------------------------------------------------
+*/
+
+// Swagger Documentation - редирект на Swagger UI
+Route::get('/docs', function () {
+    return redirect()->to('/spectrum/openapi.html');
+});
+
+// Public routes (без авторизации)
+Route::post('/register', [AuthController::class, 'register'])->name('api.register');
+Route::post('/login', [AuthController::class, 'login'])->name('api.login');
+
+// Protected routes (требуют авторизации)
+Route::middleware('auth:sanctum')->group(function () {
+    // Auth
+    Route::post('/logout', [AuthController::class, 'logout'])->name('api.logout');
+    Route::get('/user', [AuthController::class, 'user'])->name('api.user');
+
+    // Posts
+    Route::get('/posts', [PostController::class, 'index'])->name('api.posts.index');
+    Route::post('/posts', [PostController::class, 'store'])->name('api.posts.store');
+    Route::get('/posts/{post}', [PostController::class, 'show'])->name('api.posts.show');
+    Route::put('/posts/{post}', [PostController::class, 'update'])->name('api.posts.update');
+    Route::delete('/posts/{post}', [PostController::class, 'destroy'])->name('api.posts.destroy');
+});
+

4.2 Список Endpoints

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
МетодEndpointОписаниеАвторизация
POST/api/registerРегистрацияНет
POST/api/loginВходНет
POST/api/logoutВыходДа
GET/api/userТекущий пользовательДа
GET/api/postsСписок постовДа
POST/api/postsСоздание постаДа
GET/api/posts/{id}Просмотр постаДа
PUT/api/posts/{id}Обновление постаДа
DELETE/api/posts/{id}Удаление постаДа
GET/api/docsOpenAPI спецификацияНет
+
+

5. Использование API

+

5.1 Регистрация

+
curl -X POST http://la.test/api/register \
+  -H "Content-Type: application/json" \
+  -d '{
+    "name": "John Doe",
+    "email": "john@example.com",
+    "password": "password123",
+    "password_confirmation": "password123"
+  }'
+

5.2 Вход

+
curl -X POST http://la.test/api/login \
+  -H "Content-Type: application/json" \
+  -d '{
+    "email": "john@example.com",
+    "password": "password123"
+  }'
+

5.3 Создание поста

+
curl -X POST http://la.test/api/posts \
+  -H "Content-Type: application/json" \
+  -H "Authorization: Bearer {token}" \
+  -d '{
+    "title": "My First Post",
+    "content": "This is the content of my post."
+  }'
+

5.4 Выход

+
curl -X POST http://la.test/api/logout \
+  -H "Authorization: Bearer {token}"
+

+

6. Swagger документация

+

6.1 Доступ к документации

+

Документация Swagger UI доступна по адресу:

+
{APP_URL}/api/docs
+

Например: http://la.test/api/docs

+

6.2 Генерация документации

+

Laravel Spectrum генерирует готовый HTML с Swagger UI:

+
php artisan spectrum:generate --format=html
+

Это создаёт файл public/spectrum/openapi.html.

+

6.3 Настройка доступа

+

При генерации с флагом --format=html symlink создаётся автоматически.

+

Если symlink отсутствует, создайте его вручную:

+
ln -sf /home/user/www/lara/storage/app/spectrum /home/user/www/lara/public/spectrum
+

6.4 Файлы документации

+
public/
+└── spectrum/
+    ├── openapi.html        # Swagger UI (генерируется с --format=html)
+    └── openapi.json        # OpenAPI спецификация
+

6.5 Маршрут для документации

+
// routes/api.php
+
+// Swagger Documentation
+Route::get('/docs', function () {
+    return redirect()->to('/spectrum/openapi.html');
+});
+

+

7. Команды Laravel Spectrum

+

7.1 Основные команды

+

Laravel Spectrum поставляется с готовыми командами для работы с документацией.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
КомандаОписание
php artisan spectrum:generateГенерация OpenAPI документации
php artisan spectrum:watchРежим реального времени (port 8080)
php artisan spectrum:mockЗапуск mock сервера (port 8081)
php artisan spectrum:cache clearОчистка кэша анализа
+

7.2 Генерация документации

+
# Генерация документации в формате HTML
+php artisan spectrum:generate --format=html
+

Вывод:

+
🚀 Generating API documentation...
+🔍 Analyzing routes...
+Found 9 API routes
+📝 Generating OpenAPI specification...
+✅ Documentation generated: /home/user/www/lara/public/spectrum/openapi.html
+⏱️  Generation completed in 0.17 seconds
+💾 Cache: 5 files, 7.04 KB
+✅ Documentation generated successfully!
+

7.3 Режим реального времени

+
# Запуск режима watch
+php artisan spectrum:watch
+
+

7.4 Mock сервер

+
# Запуск mock API сервера
+php artisan spectrum:mock
+
+

7.5 Обновление документации

+

После добавления новых endpointов:

+
# Очистить кэш и перегенерировать
+php artisan spectrum:cache clear
+php artisan spectrum:generate
+

+

8. Политики доступа

+

8.1 PostPolicy

+
// app/Policies/PostPolicy.php
+
+namespace App\Policies;
+
+use App\Models\Post;
+use App\Models\User;
+
+class PostPolicy
+{
+    public function update(User $user, Post $post): bool
+    {
+        return $user->id === $post->user_id;
+    }
+
+    public function delete(User $user, Post $post): bool
+    {
+        return $user->id === $post->user_id;
+    }
+}
+

8.2 Регистрация политики

+

Политики автоматически обнаруживаются Laravel. Для ручной регистрации:

+
// app/Providers/AuthServiceProvider.php
+
+namespace App\Providers;
+
+use App\Models\Post;
+use App\Policies\PostPolicy;
+use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
+
+class AuthServiceProvider extends ServiceProvider
+{
+    protected $policies = [
+        Post::class => PostPolicy::class,
+    ];
+
+    public function boot(): void
+    {
+        $this->registerPolicies();
+    }
+}
+

+

9. Конфигурация .env

+
APP_NAME=Laravel
+APP_ENV=local
+APP_KEY=base64:...
+APP_URL=http://la.test
+
+DB_CONNECTION=sqlite
+
+SESSION_DRIVER=database
+

+

10. Тестирование

+
# Проверка списка маршрутов
+php artisan route:list --path=api
+
+# Генерация документации
+php artisan spectrum:generate
+
+# Запуск сервера
+php artisan serve
+

+

Troubleshooting

+

Проблема: 401 Unauthorized

+

Решение:

+
    +
  1. Проверьте токен в заголовке Authorization: Bearer {token}
  2. +
  3. Убедитесь, что используете префикс Bearer
  4. +
+

Проблема: 419 CSRF Error

+

Решение:
+Уберите statefulApi() из middleware в bootstrap/app.php:

+
->withMiddleware(function (Middleware $middleware): void {
+    // Не используйте statefulApi() для token-based API
+})
+

Проблема: Rate limiter not defined

+

Решение:
+Не используйте throttleApi():

+
// Неправильно
+$middleware->throttleApi();
+
+// Правильно (оставить пустым или удалить)
+$middleware->api();
+

+

Заключение

+

Теперь у вас есть полностью настроенный API с:

+ +

Команды для работы:

+
php artisan spectrum:generate  # Генерация документации
+php artisan spectrum:watch     # Режим разработки
+php artisan spectrum:mock     # Mock сервер
+

Документация: http://la.test/api/docs

+ +
+ + + + + + + + + + \ No newline at end of file diff --git a/SANCTUM_SETUP_GUIDE.md b/SANCTUM_SETUP_GUIDE.md index 16092cc..7d9bafc 100644 --- a/SANCTUM_SETUP_GUIDE.md +++ b/SANCTUM_SETUP_GUIDE.md @@ -203,7 +203,182 @@ class PostResource extends JsonResource } ``` -### 3.3 Form Requests +### 3.3 API Controllers + +**Laravel Spectrum** автоматически анализирует контроллеры для генерации документации. + +#### AuthController + +```php +// app/Http/Controllers/Api/AuthController.php + +namespace App\Http\Controllers\Api; + +use App\Http\Controllers\Controller; +use App\Http\Requests\LoginRequest; +use App\Http\Requests\RegisterRequest; +use App\Http\Resources\UserResource; +use App\Models\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Hash; + +class AuthController extends Controller +{ + /** + * Register a new user. + */ + public function register(RegisterRequest $request): JsonResponse + { + $user = User::create([ + 'name' => $request->name, + 'email' => $request->email, + 'password' => Hash::make($request->password), + ]); + + $token = $user->createToken('auth-token')->plainTextToken; + + return response()->json([ + 'message' => 'User registered successfully', + 'user' => new UserResource($user), + 'token' => $token, + ], 201); + } + + /** + * Login user. + */ + public function login(LoginRequest $request): JsonResponse + { + $user = User::where('email', $request->email)->first(); + + if (!$user || !Hash::check($request->password, $user->password)) { + return response()->json([ + 'message' => 'Invalid credentials', + ], 401); + } + + $token = $user->createToken('auth-token')->plainTextToken; + + return response()->json([ + 'message' => 'Login successful', + 'user' => new UserResource($user), + 'token' => $token, + ]); + } + + /** + * Logout user. + */ + public function logout(Request $request): JsonResponse + { + $request->user()->currentAccessToken()->delete(); + + return response()->json([ + 'message' => 'Logged out successfully', + ]); + } + + /** + * Get authenticated user. + */ + public function user(Request $request): JsonResponse + { + return response()->json([ + 'user' => new UserResource($request->user()), + ]); + } +} +``` + +#### PostController + +```php +// app/Http/Controllers/Api/PostController.php + +namespace App\Http\Controllers\Api; + +use App\Http\Controllers\Controller; +use App\Http\Requests\StorePostRequest; +use App\Http\Requests\UpdatePostRequest; +use App\Http\Resources\PostCollection; +use App\Http\Resources\PostResource; +use App\Models\Post; +use Illuminate\Http\JsonResponse; + +class PostController extends Controller +{ + /** + * Display a listing of posts. + */ + public function index(): PostCollection + { + $posts = Post::with('user')->latest()->paginate(15); + return new PostCollection($posts); + } + + /** + * Store a newly created post. + */ + public function store(StorePostRequest $request): JsonResponse + { + $post = Post::create([ + 'title' => $request->title, + 'content' => $request->content, + 'user_id' => $request->user()->id, + ]); + + $post->load('user'); + + return response()->json([ + 'message' => 'Post created successfully', + 'post' => new PostResource($post), + ], 201); + } + + /** + * Display the specified post. + */ + public function show(Post $post): JsonResponse + { + $post->load('user'); + return response()->json([ + 'post' => new PostResource($post), + ]); + } + + /** + * Update the specified post. + */ + public function update(UpdatePostRequest $request, Post $post): JsonResponse + { + $this->authorize('update', $post); + + $post->update($request->validated()); + + return response()->json([ + 'message' => 'Post updated successfully', + 'post' => new PostResource($post), + ]); + } + + /** + * Remove the specified post. + */ + public function destroy(Post $post): JsonResponse + { + $this->authorize('delete', $post); + + $post->delete(); + + return response()->json([ + 'message' => 'Post deleted successfully', + ]); + } +} +``` + +### 3.4 Form Requests **Laravel Spectrum** автоматически определяет правила валидации из Form Requests.