← Назад к вопросам

Как реализовать аутентификацию?

2.0 Middle🔥 191 комментариев
#REST API и HTTP#Безопасность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как реализовать аутентификацию

Основные подходы

Аутентификация - это проверка, что пользователь это действительно тот, кем он себя представляет.

1. Username + Password (Basic Auth)

Самый простой, но небезопасный способ:

from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import hashlib

app = FastAPI()
security = HTTPBasic()

@app.get("/secure")
def read_secure(credentials: HTTPBasicCredentials = Depends(security)):
    username = credentials.username
    password = credentials.password
    
    # Проверяем в БД
    user = get_user_from_db(username)
    if not user or not verify_password(password, user.password_hash):
        raise HTTPException(status_code=401, detail="Invalid credentials")
    
    return {"message": f"Hello {username}"}

Минусы: пароль отправляется с каждым запросом.

2. JWT (JSON Web Token) - самый популярный

Вместо отправления пароля каждый раз:

  1. Клиент отправляет пароль один раз
  2. Сервер выдаёт JWT токен
  3. Клиент отправляет токен в каждом запросе
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthCredentials
from datetime import datetime, timedelta
import jwt

app = FastAPI()
security = HTTPBearer()

SECRET_KEY = "your-secret-key-here"
ALGORITHM = "HS256"

# Выдать токен
@app.post("/login")
def login(username: str, password: str):
    user = get_user_from_db(username)
    if not user or not verify_password(password, user.password_hash):
        raise HTTPException(status_code=401, detail="Invalid credentials")
    
    # Создаём JWT
    payload = {
        "sub": user.id,
        "username": user.username,
        "exp": datetime.utcnow() + timedelta(hours=24)
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
    return {"access_token": token, "token_type": "bearer"}

# Проверить токен
def get_current_user(credentials: HTTPAuthCredentials = Depends(security)):
    token = credentials.credentials
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_id = payload.get("sub")
        if user_id is None:
            raise HTTPException(status_code=401, detail="Invalid token")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")
    
    user = get_user_from_db_by_id(user_id)
    if user is None:
        raise HTTPException(status_code=401, detail="User not found")
    
    return user

# Защищённый эндпоинт
@app.get("/me")
def read_user(current_user = Depends(get_current_user)):
    return {"id": current_user.id, "username": current_user.username}

Плюсы: токен самоконтролируемый, не нужно проверять БД на каждый запрос. Минусы: нельзя отозвать токен пока он не истёк (нужен blacklist).

3. Refresh tokens (более безопасно)

Два токена:

  • Access token - короткоживущий (15 минут)
  • Refresh token - долгоживущий (7 дней)
@app.post("/login")
def login(username: str, password: str):
    user = authenticate(username, password)
    
    # Короткоживущий access token
    access_token = create_token(
        data={"sub": user.id},
        expires_delta=timedelta(minutes=15)
    )
    
    # Долгоживущий refresh token
    refresh_token = create_token(
        data={"sub": user.id, "type": "refresh"},
        expires_delta=timedelta(days=7)
    )
    
    return {
        "access_token": access_token,
        "refresh_token": refresh_token,
        "token_type": "bearer"
    }

@app.post("/refresh")
def refresh_access_token(refresh_token: str):
    # Проверяем refresh token
    try:
        payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM])
        if payload.get("type") != "refresh":
            raise HTTPException(status_code=401, detail="Invalid token type")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid refresh token")
    
    # Выдаём новый access token
    user_id = payload.get("sub")
    new_access_token = create_token(
        data={"sub": user_id},
        expires_delta=timedelta(minutes=15)
    )
    
    return {"access_token": new_access_token, "token_type": "bearer"}

4. Session-based (для веб сайтов)

from fastapi.responses import JSONResponse
from itsdangerous import URLSafeTimedSerializer

app = FastAPI()
sessions = {}  # В реальности - Redis

@app.post("/login")
def login(username: str, password: str):
    user = authenticate(username, password)
    
    session_id = generate_session_id()
    sessions[session_id] = {
        "user_id": user.id,
        "created_at": datetime.utcnow(),
        "expires_at": datetime.utcnow() + timedelta(days=7)
    }
    
    response = JSONResponse({"message": "Logged in"})
    response.set_cookie(
        key="session_id",
        value=session_id,
        httponly=True,  # Не доступна JavaScript
        secure=True,     # Только HTTPS
        samesite="strict"
    )
    return response

@app.get("/me")
def get_user(request: Request):
    session_id = request.cookies.get("session_id")
    if not session_id or session_id not in sessions:
        raise HTTPException(status_code=401, detail="Not authenticated")
    
    session = sessions[session_id]
    if datetime.utcnow() > session["expires_at"]:
        del sessions[session_id]
        raise HTTPException(status_code=401, detail="Session expired")
    
    user = get_user_from_db_by_id(session["user_id"])
    return user

5. OAuth2 (внешние сервисы)

Делегируем аутентификацию Google, GitHub и т.д.:

from authlib.integrations.starlette_client import OAuth

oauth = OAuth()
oauth.register(
    name='google',
    client_id='YOUR_CLIENT_ID',
    client_secret='YOUR_CLIENT_SECRET',
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid profile email'}
)

@app.get("/login/google")
async def login_google(request: Request):
    redirect_uri = request.url_for('auth_callback')
    return await oauth.google.authorize_redirect(request, redirect_uri)

@app.get("/auth/callback")
async def auth_callback(request: Request):
    token = await oauth.google.authorize_access_token(request)
    user_info = token.get('userinfo')
    
    # Создаём или обновляем пользователя в БД
    user = create_or_update_user(user_info)
    
    # Выдаём свой JWT
    access_token = create_token(data={"sub": user.id})
    return {"access_token": access_token}

Best Practices

  1. Никогда не храни пароли в открытом виде
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# При регистрации
password_hash = pwd_context.hash(password)

# При проверке
is_correct = pwd_context.verify(password, password_hash)
  1. Используй HTTPS - всегда!
  2. Токены в secure cookies - не в localStorage
  3. HTTPOnly флаг - защита от XSS
  4. CSRF защита - если используешь cookies
  5. Rate limiting на /login - защита от brute force
  6. Двухфакторная аутентификация - для критичных операций

Сравнение подходов

ПодходUse caseПлюсыМинусы
JWTAPI, мобильноеМасштабируемо, statelessСложнее отозвать
SessionВеб сайтыПросто, легко отозватьНужна серверная сессия
OAuth2Социальные сетиБезопасно, удобноЗависимость от провайдера

Обычно для веб-приложений: JWT + refresh tokens или session-based. Для API: JWT.

Как реализовать аутентификацию? | PrepBro