← Назад к вопросам
Как реализовать аутентификацию?
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) - самый популярный
Вместо отправления пароля каждый раз:
- Клиент отправляет пароль один раз
- Сервер выдаёт JWT токен
- Клиент отправляет токен в каждом запросе
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
- Никогда не храни пароли в открытом виде
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)
- Используй HTTPS - всегда!
- Токены в secure cookies - не в localStorage
- HTTPOnly флаг - защита от XSS
- CSRF защита - если используешь cookies
- Rate limiting на /login - защита от brute force
- Двухфакторная аутентификация - для критичных операций
Сравнение подходов
| Подход | Use case | Плюсы | Минусы |
|---|---|---|---|
| JWT | API, мобильное | Масштабируемо, stateless | Сложнее отозвать |
| Session | Веб сайты | Просто, легко отозвать | Нужна серверная сессия |
| OAuth2 | Социальные сети | Безопасно, удобно | Зависимость от провайдера |
Обычно для веб-приложений: JWT + refresh tokens или session-based. Для API: JWT.