Basic Auth и API keys: где аутентификация начинается
В этом уроке мы рассмотрим самые простые способы аутентификации в HTTP API: Basic Auth (часть стандарта HTTP с 1996 года) и API keys (фактический стандарт у большинства публичных API). Эти два механизма — то, с чем Junior DE сталкивается ежедневно: 80% машинно-машинных интеграций ходят либо с Basic Auth, либо с API key в заголовке.
Главная мысль урока: оба механизма опасны при неправильной обработке (особенно Basic Auth — base64 это не шифрование), но при дисциплинированном использовании (HTTPS, ротация, секретное хранение) — это рабочий и удобный инструмент.
TLS handshake: как HTTPS защищает credentials
HTTP Basic Auth (RFC 7617): base64 это не шифрование
Basic Auth — старейший механизм аутентификации в HTTP, описан в RFC 7617 (актуализирован в 2015, оригинал из RFC 1945, 1996). Принцип примитивен: клиент кодирует пару username:password в base64 и кладёт в заголовок Authorization.
GET /api/users HTTP/1.1
Host: api.example.com
Authorization: Basic YWxpY2U6c2VjcmV0MTIz
YWxpY2U6c2VjcmV0MTIz — это base64 от строки alice:secret123. Декодировать в обратную сторону тривиально:
import base64
# Кодирование
credentials = "alice:secret123"
encoded = base64.b64encode(credentials.encode()).decode()
print(encoded) # YWxpY2U6c2VjcmV0MTIz
# Декодирование (любой человек может это сделать с заголовком)
decoded = base64.b64decode("YWxpY2U6c2VjcmV0MTIz").decode()
print(decoded) # alice:secret123
Ключевой факт: base64 — это кодирование, не шифрование. Любой, кто перехватит запрос (или прочитает в логе), за одну команду получает оригинальные username:password. Без HTTPS Basic Auth — это публичная отправка пароля.
Никогда не используйте Basic Auth по plain HTTP. Это эквивалентно отправке пароля открытым текстом. Любой инструмент сетевого мониторинга его прочитает.
Как браузер делает Basic Auth: 401 -> диалог -> повтор
В классическом сценарии Basic Auth сервер возвращает 401 без Authorization, заставляя клиента повторить запрос с правильным заголовком.
В Python-клиенте мы обычно сразу шлём Authorization-заголовок без 401-handshake — это экономит один сетевой round-trip:
import requests
from requests.auth import HTTPBasicAuth
# Способ 1: через auth-параметр (рекомендуется)
response = requests.get(
"https://api.example.com/users",
auth=HTTPBasicAuth("alice", "secret123"),
)
# Способ 2: то же через tuple
response = requests.get(
"https://api.example.com/users",
auth=("alice", "secret123"),
)
# Способ 3: вручную (для понимания)
import base64
creds = base64.b64encode(b"alice:secret123").decode()
response = requests.get(
"https://api.example.com/users",
headers={"Authorization": f"Basic {creds}"},
)
Все три варианта приводят к идентичному HTTP-запросу. На практике первый — самый чистый.
API keys: «токен на каждый клиент»
API key — это случайно сгенерированный секретный токен, который сервер выдаёт клиенту (часто через личный кабинет на сайте API). Клиент шлёт ключ при каждом запросе, сервер по ключу определяет «какой клиент это и какие у него права».
В отличие от Basic Auth, API key:
- Не предполагает логин/пароль пользователя — это про идентификацию клиента (приложения/сервиса), не человека
- Один клиент может иметь несколько ключей (live/test, прод/стейдж, ключи для разных environments)
- Легко отзывается: скомпрометированный ключ можно «обнулить» в одном месте, не меняя пароли пользователей
API keys — самый распространённый механизм для машинно-машинных интеграций. Stripe, OpenAI, SendGrid, Twilio, AWS — все начинаются с API key.
Где передавать API key: header vs query string vs body
Есть три места, куда обычно кладут API key. Разница принципиальна с точки зрения безопасности.
Header — стандарт индустрии
Два самых распространённых формата заголовка:
# Вариант 1: Authorization: Bearer (унифицированный, RFC 6750)
Authorization: Bearer sk_live_abc123def456ghi789
# Вариант 2: X-API-Key (проще, но не стандартизирован)
X-API-Key: sk_live_abc123def456ghi789
Современные API (Stripe, OpenAI, SendGrid) используют формат Authorization: Bearer. Старые или внутренние API часто используют X-API-Key. В Python разница невелика:
import requests
# Вариант с Authorization
response = requests.get(
"https://api.openai.com/v1/models",
headers={"Authorization": "Bearer sk-proj-abc123..."},
)
# Вариант с X-API-Key
response = requests.get(
"https://api.example.com/v1/data",
headers={"X-API-Key": "abc123def456"},
)
Query string — опасно
# НЕ ДЕЛАЙТЕ ТАК
response = requests.get(
f"https://api.example.com/v1/data?api_key={KEY}",
)
Почему опасно:
- Access logs — Nginx/Apache по умолчанию логируют URL целиком. Ваш ключ оседает в файле
/var/log/nginx/access.logна каждом промежуточном прокси - Referer header — если ответ содержит ссылку, по которой кликнут, новый сайт получит исходный URL в Referer
- Browser history — если кто-то откроет такой URL в браузере, ключ сохранится в истории
- CDN кэш — некоторые CDN кэшируют по полному URL, ключ попадает в кэш
Реальный инцидент: GitHub находил утёкшие API keys через простой поиск ?api_key= в публичных репозиториях логов и архивов. Десятки тысяч ключей утекли таким образом.
Если API партнёра требует ключ в query string — это плохо спроектированный API. По возможности используйте header. Если выбора нет — постарайтесь хотя бы не логировать URL целиком на своей стороне и регулярнее ротируйте ключ.
Префиксы ключей: sk_live_, pk_test_ и зачем они
Современные API (Stripe пионер, остальные подтянулись) используют префиксы в API key, передающие метаданные. Например:
sk_live_...— secret key, production environmentsk_test_...— secret key, test/sandbox environmentpk_live_...— publishable key (можно класть во frontend), productionpk_test_...— publishable key, testrestk_...— restricted key (Stripe, ограниченные права)
Префиксы — это convention, не часть стандарта. Польза:
- Сразу видно, какой environment (предотвращает «отправил тестовый webhook на прод»)
- Сразу видно, можно ли публиковать (publishable key — да, secret — никогда)
- Поиск утечек через regex по префиксу: GitHub Secret Scanning ищет
sk_live_в публичных репо и оповещает владельцев
Если делаете свой API — стоит копировать этот паттерн.
Когда использовать Basic Auth, а когда API keys
Оба механизма работают, но в разных контекстах.
В работе Junior DE Basic Auth встречается реже, в основном для подключения к внутренним инструментам компании (admin panel, Grafana, прокси). API keys — постоянно: каждый external API, в который ходит ваш ETL, использует API key (или OAuth2, который мы рассмотрим в следующих уроках).
Ротация ключей: как и почему
Любой долгоживущий секрет рискует утечь: разработчик случайно закоммитил .env в публичный репо, лог утёк через bug-репорт, ноутбук с ключами в ~/.config/ потерян. Защита — ротация: периодическая смена ключей.
Цикл ротации:
Частота ротации: индустриальный стандарт — 90 дней для секретов средней критичности, 30 дней для высокочувствительных. Многие провайдеры (AWS IAM, Vault) автоматизируют ротацию, многие — нет, и тогда это ваша задача (cron + runbook).
Аварийная ротация: если есть подозрение на утечку — ротируйте немедленно, без overlap. Лучше короткий даунтайм, чем взлом.
Хранение ключей: что, где, как
Ключи нельзя:
- Коммитить в git (даже в private репо)
- Логировать (даже фрагменты)
- Шарить в Slack/email открытым текстом
- Хранить в plain text на ноутбуке
Ключи можно/нужно:
- Хранить в
.envфайле, добавленном в.gitignore - Использовать менеджер секретов (1Password, AWS Secrets Manager, HashiCorp Vault, Doppler)
- В CI/CD — через secrets repository (GitHub Actions secrets, GitLab CI/CD variables)
- На локальной машине — keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)
В Python типичная загрузка ключа из .env:
import os
from dotenv import load_dotenv
import requests
load_dotenv() # подгружает .env файл
api_key = os.environ["STRIPE_API_KEY"]
response = requests.get(
"https://api.stripe.com/v1/customers",
headers={"Authorization": f"Bearer {api_key}"},
)
.env файл в проекте:
STRIPE_API_KEY=sk_live_abc123...
OPENAI_API_KEY=sk-proj-def456...
.gitignore:
.env
.env.local
.env.*.local
Если случайно закоммитили ключ в git: ротируйте немедленно. git rm и rewrite history НЕ помогает — git rewrite видим в reflog, копии репозитория уже у других людей, история кешируется в GitHub. Ротация — единственный надёжный способ.
Сравнение: Basic Auth vs API key vs Bearer token
Чтобы держать в голове общую картину — таблица различий с теми механизмами, которые мы рассмотрим в следующих уроках.
В реальной работе DE использует все четыре, но в разных пропорциях: Basic Auth — изредка для внутренних tools, API keys — на каждом дне для внешних API, Bearer/JWT и OAuth2 — для интеграций с большими платформами (Google, Microsoft, GitHub).
Полный пример: интеграция с API через requests
Соберём practical-пример, который Junior DE напишет в первый день работы. Допустим, нужно забрать список customers из API через API key.
import os
import logging
from dotenv import load_dotenv
import requests
load_dotenv()
logger = logging.getLogger(__name__)
class APIClient:
def __init__(self, api_key: str, base_url: str = "https://api.example.com/v1"):
self.base_url = base_url
self.session = requests.Session()
# Заголовок применяется ко всем запросам через session
self.session.headers["Authorization"] = f"Bearer {api_key}"
# Нельзя логировать api_key -- даже в debug
logger.info("APIClient initialized for %s", base_url)
def list_customers(self, limit: int = 100) -> list[dict]:
response = self.session.get(
f"{self.base_url}/customers",
params={"limit": limit},
timeout=10,
)
response.raise_for_status()
return response.json()["data"]
def main():
api_key = os.environ["EXAMPLE_API_KEY"]
client = APIClient(api_key=api_key)
customers = client.list_customers(limit=50)
print(f"Got {len(customers)} customers")
if __name__ == "__main__":
main()
Что здесь критично:
- Ключ читается из
os.environ, не хардкод - Используется
Session— заголовок устанавливается один раз, применяется ко всем запросам - В логе нигде не появляется
api_key(только факт инициализации) timeout=10— защита от зависанияraise_for_status()— чтобы не молча обрабатывать 401/500
Итоги урока
Basic Auth — это base64-кодирование username:password в заголовке Authorization. Не шифрование: без HTTPS пароль виден всем. RFC 7617, для machine-to-machine редко используется, для admin tools — иногда.
API keys — случайные токены для идентификации клиента (приложения/сервиса). Передаются в заголовке Authorization: Bearer KEY или X-API-Key: KEY. Никогда не в query string — попадает в логи. Префиксы (sk_live_, pk_test_) помогают различать environments и упрощают поиск утечек.
Безопасное обращение с ключами: хранение в .env (с .gitignore) или secrets manager, ротация раз в 30-90 дней, аварийная ротация при подозрении на утечку. Никогда не коммитить, не логировать, не шарить открытым текстом.
В следующем уроке перейдём к Bearer-токенам и JWT — следующая ступень аутентификации, добавляющая срок жизни и метаданные в сам токен.