Skip to content

Глава 6.3: Базовая аутентификация

Время изучения: 1 час


1. Аутентификация API: Пропуск в ЦУП

Аутентификация — это процесс проверки личности пользователя. В отличие от сайтов с сессиями и куки, stateless (не хранящие состояние) API обычно используют токены.

Процесс выглядит так:

  1. Пользователь отправляет свои логин и пароль на специальный эндпоинт (например, /login).
  2. Сервер проверяет их. Если все верно, он генерирует уникальный, зашифрованный токен (длинную строку) и отправляет его обратно.
  3. При каждом последующем запросе к защищенным ресурсам (например, POST /planets) пользователь должен прикреплять этот токен в заголовке Authorization.
  4. Сервер проверяет токен на валидность и, если он корректен, выполняет запрос.

💡 Космическая аналогия:

  • Логин/пароль = Ваш биометрический скан для получения пропуска.
  • Токен = Электронный пропуск (ID-карта), который вы получаете на входе в ЦУП.
  • Заголовок Authorization: Bearer <токен> = Вы прикладываете свой пропуск к считывателю у каждой защищенной двери.
  • Защищенные эндпоинты (POST, PUT, DELETE) = Двери в серверную или к пульту управления запуском.

2. Аутентификация в Laravel: Sanctum

Laravel предлагает элегантное решение для аутентификации API — Laravel Sanctum. Он идеально подходит для SPA (одностраничных приложений), мобильных приложений и простых токен-based API.

Шаг 1: Установка и настройка Sanctum

Sanctum уже установлен в стандартном приложении Laravel, но проверим конфигурацию.

  1. Публикация конфигурации (если еще не сделали):
    php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
    
  2. Запуск миграций (создаст таблицу personal_access_tokens):
    php artisan migrate
    
  3. Добавление трейта в модель User: Откройте app/Models/User.php и убедитесь, что он использует трейт HasApiTokens.
    // app/Models/User.php
    use Laravel\Sanctum\HasApiTokens;
    
    class User extends Authenticatable
    {
        use HasApiTokens, HasFactory, Notifiable;
        // ...
    }
    

Шаг 2: Создание эндпоинта для выдачи токенов Нам нужен роут, куда пользователь будет отправлять логин/пароль.

Добавьте в routes/api.php:

// routes/api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use Illuminate\Validation\ValidationException;

Route::post('/login', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
    ]);

    $user = User::where('email', $request->email)->first();

    if (! $user || ! Hash::check($request->password, $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['Учетные данные неверны.'],
        ]);
    }

    // Возвращаем токен
    return response()->json([
        'token' => $user->createToken('api-token')->plainTextToken
    ]);
});

Для теста можно создать пользователя через сидер или Tinker.

Шаг 3: Защита роутов Теперь защитим наши CRUD-операции. Изменим routes/api.php:

// routes/api.php
use App\Http\Controllers\PlanetController;

// Публичный роут для просмотра планет
Route::get('/planets', [PlanetController::class, 'index']);
Route::get('/planets/{planet}', [PlanetController::class, 'show']);

// Группа защищенных роутов
Route::middleware('auth:sanctum')->group(function () {
    Route::post('/planets', [PlanetController::class, 'store']);
    Route::put('/planets/{planet}', [PlanetController::class, 'update']);
    Route::delete('/planets/{planet}', [PlanetController::class, 'destroy']);

    // Роут для выхода (удаления токена)
    Route::post('/logout', function (Request $request) {
        $request->user()->currentAccessToken()->delete();
        return response()->json(['message' => 'Вы успешно вышли'], 200);
    });
});

Middleware auth:sanctum будет проверять наличие валидного токена в заголовке Authorization.


3. Аутентификация в FastAPI: OAuth2 и JWT

В FastAPI нет встроенной системы аутентификации, но есть мощные инструменты для ее реализации. Стандарт де-факто — OAuth2 с JWT-токенами.

Шаг 1: Установка зависимостей

pip install "python-jose[cryptography]" "passlib[bcrypt]" "python-multipart"

  • python-jose: для создания и проверки JWT-токенов.
  • passlib: для хэширования и проверки паролей.
  • python-multipart: для обработки данных из форм (username и password).

Шаг 2: Создание модуля безопасности (security.py) Это хорошая практика — вынести всю логику аутентификации в отдельный файл.

Создайте файл security.py:

# security.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta, timezone

# --- Настройки ---
SECRET_KEY = "your-super-secret-key-that-is-long-and-random" # ⚠️ Замените на свой ключ!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# --- Утилиты ---
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/login")

# --- Функции ---
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
    return pwd_context.hash(password)

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

# --- Функция-зависимость для проверки токена ---
def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Не удалось проверить учетные данные",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    # Здесь можно вернуть пользователя из БД, пока просто вернем имя
    return {"username": username}

Шаг 3: Интеграция в main.py Теперь подключим это к нашему приложению.

  1. Создадим эндпоинт /login:

    # main.py
    from fastapi.security import OAuth2PasswordRequestForm
    from fastapi import Depends, APIRouter
    from . import security # Импортируем наш модуль
    
    # ... ваш код FastAPI ...
    router = APIRouter(prefix="/api/v1")
    
    @router.post("/login")
    def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
        # Здесь должна быть проверка пользователя в БД
        # Для примера, у нас есть один тестовый юзер
        is_user_valid = (form_data.username == "testuser" and
                         security.verify_password("testpass", security.get_password_hash("testpass")))
    
        if not is_user_valid:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Неверное имя пользователя или пароль",
            )
        access_token = security.create_access_token(data={"sub": form_data.username})
        return {"access_token": access_token, "token_type": "bearer"}
    
    # ...
    app.include_router(router)
    

  2. Защитим эндпоинты:

    Используем нашу зависимость get_current_user.

    # main.py или в вашем роутере планет
    
    @router.post("/planets", status_code=status.HTTP_201_CREATED)
    def create_planet(
        planet: PlanetCreate,
        current_user: dict = Depends(security.get_current_user) # <-- Защита!
    ):
        # Логика создания планеты...
        print(f"Пользователь {current_user['username']} создает планету.")
        # ...
        return new_planet
    
    # Так же защищаем PUT и DELETE
    


4. Использование токенов на фронтенде

Наш фронтенд теперь должен сначала получить токен, сохранить его (например, в localStorage) и прикреплять к каждому защищенному запросу.

Пример на JavaScript (fetch):

// 1. Логинимся
async function login(email, password) {
    const response = await fetch('http://localhost:8001/api/login', { // Адрес Laravel API
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({email, password})
    });
    const data = await response.json();

    if (data.token) {
        localStorage.setItem('api_token', data.token); // Сохраняем токен
    }
}

// 2. Делаем защищенный запрос
async function createPlanet(planetData) {
    const token = localStorage.getItem('api_token');

    const response = await fetch('http://localhost:8001/api/planets', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}` // <--- Прикрепляем токен!
        },
        body: JSON.stringify(planetData)
    });
    // ...
}


Квиз для закрепления

1. Stateless API чаще всего используют для аутентификации:

2. В Laravel для защиты роутов с помощью токенов используется middleware:

3. В FastAPI для получения данных из формы логина используется зависимость:

4. Как токен передается от клиента к серверу в защищенном запросе?


🚀 Итог главы:

Вы установили "систему контроля доступа" на свои API. Теперь не каждый желающий может вносить изменения в вашу "галактическую базу данных".

  • ✅ Поняли принцип токен-based аутентификации.
  • 🔐 Реализовали выдачу токенов и защиту роутов в Laravel Sanctum.
  • ⚙️ Настроили аутентификацию на основе OAuth2 и JWT в FastAPI.
  • 🛰️ Узнали, как фронтенд должен сохранять и использовать токен.

Ваши API стали не только функциональными, но и безопасными. Однако, чтобы другие разработчики могли ими пользоваться, им нужны "инструкции по эксплуатации".

📌 Проверка:

  • Попробуйте сделать POST-запрос на /api/planets (в Laravel) или /api/v1/planets (в FastAPI) без токена с помощью Postman или Insomnia. Вы должны получить ошибку 401 Unauthorized.
  • Сделайте запрос на /login, получите токен, добавьте его в заголовок Authorization и повторите POST-запрос. Он должен выполниться успешно.