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

Как получить токен на клиенте?

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

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

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

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

Как получить токен на клиенте

Получение токена — фундаментальная операция при работе с API. Я расскажу про все варианты: от простых до enterprise решений.

1. Базовая аутентификация: username + password

На сервере (FastAPI):

from fastapi import FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from datetime import datetime, timedelta, timezone
import jwt

app = FastAPI()
security = HTTPBasic()
SECRET_KEY = "your-secret-key-change-in-prod"
ALGORITHM = "HS256"

@app.post("/api/v1/auth/token")
async def login(credentials: HTTPBasicCredentials):
    """Получить JWT токен по username и password."""
    
    # Проверяем учётные данные (в реальности — ищем в БД)
    user = authenticate_user(credentials.username, credentials.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Неверные учётные данные",
            headers={"WWW-Authenticate": "Bearer"},
        )
    
    # Генерируем JWT токен
    expire = datetime.now(timezone.utc) + timedelta(hours=1)
    payload = {
        "sub": user.id,
        "username": user.username,
        "exp": expire
    }
    
    token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
    
    return {
        "access_token": token,
        "token_type": "bearer",
        "expires_in": 3600
    }

На клиенте (Python requests):

import requests
from requests.auth import HTTPBasicAuth

# Способ 1: передача credentials в заголовок
response = requests.post(
    "https://api.example.com/api/v1/auth/token",
    auth=HTTPBasicAuth("username", "password")
)

token_data = response.json()
token = token_data["access_token"]
print(f"Получен токен: {token}")

# Способ 2: передача в body (чаще используется)
response = requests.post(
    "https://api.example.com/api/v1/auth/token",
    data={
        "username": "user@example.com",
        "password": "password123"
    }
)

if response.status_code == 200:
    token = response.json()["access_token"]
else:
    raise Exception(f"Ошибка аутентификации: {response.text}")

2. OAuth2 с Bearer токеном

На сервере (FastAPI с OAuth2):

from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
import jwt

app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

class Token(BaseModel):
    access_token: str
    token_type: str

class User(BaseModel):
    username: str
    email: str

@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    """Получить токен по OAuth2 Password Flow."""
    
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Неверные учётные данные"
        )
    
    access_token = create_access_token(user.id)
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/me", response_model=User)
async def read_users_me(token: str = Depends(oauth2_scheme)):
    """Получить текущего пользователя по токену."""
    user = verify_token(token)
    return user

На клиенте:

import requests
from requests.auth import HTTPBearer

# Шаг 1: получить токен
response = requests.post(
    "https://api.example.com/token",
    data={
        "username": "user@example.com",
        "password": "password123"
    }
)

token = response.json()["access_token"]

# Шаг 2: использовать токен в заголовке Authorization
headers = {"Authorization": f"Bearer {token}"}

response = requests.get(
    "https://api.example.com/me",
    headers=headers
)

user = response.json()
print(user)  # {"username": "...", "email": "..."}

3. API Key (простой токен)

На сервере:

from fastapi import FastAPI, Header, HTTPException

app = FastAPI()
VALID_API_KEYS = {
    "sk_test_1234567890": {"user_id": 1, "permissions": ["read", "write"]}
}

@app.get("/api/v1/data")
async def get_data(x_api_key: str = Header()):
    """Эндпоинт, защищённый API Key."""
    
    if x_api_key not in VALID_API_KEYS:
        raise HTTPException(
            status_code=403,
            detail="Неверный API ключ"
        )
    
    api_key_info = VALID_API_KEYS[x_api_key]
    return {"data": "secret", "user_id": api_key_info["user_id"]}

На клиенте:

import requests

api_key = "sk_test_1234567890"
headers = {"X-API-Key": api_key}

response = requests.get(
    "https://api.example.com/api/v1/data",
    headers=headers
)

print(response.json())

4. Refresh токены для долговечных сессий

На сервере:

from datetime import datetime, timedelta, timezone
import jwt

SECRET_KEY = "secret"
ACCESS_TOKEN_EXPIRE = timedelta(minutes=15)
REFRESH_TOKEN_EXPIRE = timedelta(days=7)

def create_tokens(user_id: str) -> dict:
    """Создаёт access и refresh токены."""
    
    # Access token (короткоживущий)
    access_expire = datetime.now(timezone.utc) + ACCESS_TOKEN_EXPIRE
    access_payload = {"sub": user_id, "type": "access", "exp": access_expire}
    access_token = jwt.encode(access_payload, SECRET_KEY, algorithm="HS256")
    
    # Refresh token (долгоживущий)
    refresh_expire = datetime.now(timezone.utc) + REFRESH_TOKEN_EXPIRE
    refresh_payload = {"sub": user_id, "type": "refresh", "exp": refresh_expire}
    refresh_token = jwt.encode(refresh_payload, SECRET_KEY, algorithm="HS256")
    
    return {
        "access_token": access_token,
        "refresh_token": refresh_token,
        "token_type": "bearer",
        "expires_in": int(ACCESS_TOKEN_EXPIRE.total_seconds())
    }

@app.post("/token")
async def login(username: str, password: str):
    """Получить access и refresh токены."""
    user = authenticate_user(username, password)
    if not user:
        raise HTTPException(status_code=401)
    
    return create_tokens(user.id)

@app.post("/token/refresh")
async def refresh_token(refresh_token: str):
    """Получить новый access токен по refresh токену."""
    
    try:
        payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=["HS256"])
        if payload.get("type") != "refresh":
            raise HTTPException(status_code=401)
        
        user_id = payload["sub"]
        new_tokens = create_tokens(user_id)
        return new_tokens
        
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Refresh токен истёк")

На клиенте:

import requests
import json
import os

class AuthClient:
    def __init__(self, api_url: str):
        self.api_url = api_url
        self.token_file = "tokens.json"
    
    def login(self, username: str, password: str):
        """Получить токены и сохранить их."""
        response = requests.post(
            f"{self.api_url}/token",
            data={"username": username, "password": password}
        )
        
        tokens = response.json()
        self._save_tokens(tokens)
        return tokens
    
    def get_access_token(self) -> str:
        """Получить действующий access токен."""
        tokens = self._load_tokens()
        
        # Если токен истёк, обновляем
        if self._is_token_expired(tokens["access_token"]):
            self.refresh_tokens()
            tokens = self._load_tokens()
        
        return tokens["access_token"]
    
    def refresh_tokens(self):
        """Обновить access токен используя refresh токен."""
        tokens = self._load_tokens()
        
        response = requests.post(
            f"{self.api_url}/token/refresh",
            data={"refresh_token": tokens["refresh_token"]}
        )
        
        new_tokens = response.json()
        self._save_tokens(new_tokens)
    
    def get_headers(self) -> dict:
        """Получить заголовки с токеном для API запроса."""
        token = self.get_access_token()
        return {"Authorization": f"Bearer {token}"}
    
    def _save_tokens(self, tokens: dict):
        with open(self.token_file, "w") as f:
            json.dump(tokens, f)
    
    def _load_tokens(self) -> dict:
        with open(self.token_file, "r") as f:
            return json.load(f)
    
    def _is_token_expired(self, token: str) -> bool:
        import jwt
        try:
            payload = jwt.decode(token, options={"verify_signature": False})
            exp = payload.get("exp")
            return datetime.fromtimestamp(exp) < datetime.now()
        except:
            return True

# Использование
auth = AuthClient("https://api.example.com")
auth.login("user@example.com", "password")

# При каждом запросе токен автоматически обновляется если нужно
response = requests.get(
    "https://api.example.com/me",
    headers=auth.get_headers()
)

5. OpenID Connect (для сложных сценариев)

from authlib.integrations.requests_client import OAuth2Session

client = OAuth2Session(
    client_id="your-client-id",
    client_secret="your-client-secret",
    redirect_uri="http://localhost:8000/callback"
)

# Получить ссылку для аутентификации
uri, state = client.create_authorization_url(
    "https://provider.com/authorize"
)

# После редиректа получить токен
token = client.fetch_token(
    "https://provider.com/token",
    authorization_response=callback_response
)

print(token["access_token"])

Рекомендации

СценарийРешение
Простой APIAPI Key в заголовке
Web приложениеOAuth2 с refresh токеном
Mobile приложениеOAuth2 + PKCE
МикросервисыJWT с долгоживущими токенами
EnterpriseOpenID Connect, SAML

Вывод

  • Никогда не передавай пароль в каждом запросе — получи токен один раз
  • Используй HTTPS при передаче токенов
  • Refresh токены для долговечных сессий
  • Кэшируй токены локально чтобы не делать лишние запросы
  • Проверяй истечение и автоматически обновляй если надо
Как получить токен на клиенте? | PrepBro