Проект без собственного сервера рождает неожиданный вопрос: где теперь жить логину и паролю. В монолите ответ был очевиден, а в бессерверном стеке за вход пользователей отвечает управляемый сервис, и выбор такого сервиса определяет архитектуру на годы вперёд. На рынке закрепились два имени: Amazon Cognito, встроенный в экосистему AWS, и Auth0, платформенно независимый сервис с собственной панелью управления. Разница между ними не в том, кто "лучше", а в том, что дешевле обслуживать в конкретном стеке: у Cognito сила в нативных интеграциях и в цене, у Auth0 в гибкости и в скорости внедрения сложных сценариев. Ниже обе системы разобраны по одинаковым осям: модель, тарифы, интеграция с API Gateway и Lambda, токены, кастомизация и сценарии выбора.
Две модели аутентификации от каталога пользователей до временных ключей AWS
Cognito под капотом содержит два независимых механизма, и путаница между ними ломает архитектуру чаще любого другого заблуждения. Пул пользователей работает как готовый каталог: регистрация, вход, восстановление пароля, многофакторная проверка, настраиваемые атрибуты профиля. Каталог построен на стандарте OIDC и выдаёт тройку токенов в формате JWT. Пул идентичности решает другую задачу: он принимает уже полученный токен от Cognito, внешнего провайдера или корпоративной системы SAML и обменивает его на временные ключи доступа к ресурсам AWS с ограниченными правами. Разница принципиальная: первый механизм отвечает на вопрос "кто вошёл", второй на вопрос "к чему этот человек теперь имеет доступ в облаке".
В связке они дают паттерн, недоступный Auth0 без дополнительной прослойки: браузер аутентифицируется в пуле пользователей, получает ID-токен, передаёт его пулу идентичности и получает временные ключи, с которыми напрямую читает файлы из S3 или пишет строки в DynamoDB, минуя собственный бэкенд. Запрос к пулу идентичности выглядит так:
{
"IdentityPoolId": "eu-central-1:1a2b3c4d-5678-90ab-cdef",
"Logins": {
"cognito-idp.eu-central-1.amazonaws.com/eu-central-1_AbCdEf": "eyJhbGciOiJSUzI1NiIs..."
}
}
Ключом в объекте Logins служит адрес эмитента токена, значением сам токен. Ответ содержит пару временных ключей с ролью, назначенной пулу, поэтому права клиента ограничиваются одной IAM-политикой вместо полноценного бэкенда. Auth0 в такой схеме остаётся провайдером идентичности: чтобы превратить его токены в ключи AWS, придётся написать функцию-посредника, которая обменяет JWT на временные креды через STS, либо доверить проверку токенов шлюзу и оставить обмен только для специальных случаев.
Auth0 устроен иначе: единая платформа с понятием тенанта. Каждый тенант изолирован, привязан к региону размещения и имеет отдельный домен, а приложения, подключения и настройки живут внутри него. За аутентификацию отвечает конвейер: Universal Login формирует страницу входа, экшены на Node.js запускаются в моменты входа и выпуска токенов, а готовые коннекторы закрывают внешние провайдеры входа и корпоративный SAML. Отдельная сила платформы связана с B2B: Organizations позволяет держать под одним тенантом десятки компаний-клиентов с раздельными настройками, брендированными страницами и приглашениями по почте. В Cognito аналогичный сценарий собирается вручную из групп и настраиваемых атрибутов, и чем больше организаций, тем заметнее разница в объёме разработки.
Тарифы и бесплатные лимиты, которые определяют выбор на старте
Модель оплаты Cognito изменилась в конце 2024 года, поэтому советы из старых статей теперь вводят в заблуждение. Раньше сервис считал только активных пользователей с одним щедрым бесплатным порогом. С 22 ноября 2024 года у пулов три уровня функций: Lite, Essentials и Plus. Бесплатный порог составляет 10 000 активных пользователей в месяц на аккаунт для пулов на уровнях Lite и Essentials, а у уровня Plus бесплатного порога нет. Уровень Plus добавляет адаптивную аутентификацию с оценкой риска, детект скомпрометированных учётных данных и вход без пароля. Переключать пул между уровнями можно в любой момент, а пулы, работавшие до перехода на новую модель, тарифицируются по прежним правилам уровня Essentials до 30 ноября 2025 года.
Активным для биллинга Cognito считается пользователь, который за календарный месяц выполнил хотя бы одну операцию аутентификации: вход, обновление токена, смена пароля. Пользователь, заходящий раз в месяц, стоит столько же, сколько заходящий ежедневно, поэтому "спящие" аудитории почти не давят на бюджет.
У Auth0 бесплатный план даёт до 25 000 активных пользователей в месяц и один собственный домен. Дальше оплата идёт пакетами по активным пользователям, для B2B-приложений на Organizations действует собственный счётчик внешних активных пользователей, а межмашинные токены для сервисных вызовов тарифицируются отдельно от людей. Стартовая арифметика проста: прототип и ранний продукт помещаются в бесплатные рамки обеих систем, а дальше счёт зависит от того, что именно растёт быстрее, база людей или число машинных вызовов.
Аналитика здесь важнее цифр. Cognito при равной аудитории внутри AWS почти всегда дешевле, зато Auth0 продаёт готовностью: детект утёкших паролей, единая страница входа, организации и мониторинг аномалий входят в продукт, а не пишутся командой. Недельная ставка разработчика легко перекрывает разницу в тарифе, если две из этих функций входят в план проекта.
Интеграция с API Gateway и лямбда-функциями без собственного сервера
Главный плюс Cognito в AWS-стеке: шлюз понимает его токены без единой строки кода. В HTTP API настраивается JWT-авторайзер: указывается эмитент и аудитория, шлюз сам забирает публичный ключ из JWKS-эндпоинта эмитента, кэширует его и проверяет у каждого запроса подпись, срок действия и утверждения iss и aud. Промах кэша ключей добавляет задержку к одному запросу, поэтому авторайзер практически не виден в метриках. Создаётся такая защита одной командой:
aws apigatewayv2 create-authorizer \\
--api-id abcd1234 \\
--name jwt-pool \\
--authorizer-type JWT \\
--identity-source "$request.header.Authorization" \\
--jwt-configuration Audience=7abc1234,Issuer=https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_AbCdEf
В REST API для тех же целей существует отдельный тип авторайзера с привязкой к пулу пользователей, а балансировщик ALB умеет проверять токены Cognito на уровне действия authenticate. Auth0 подключается тем же JWT-авторайзером, потому что для шлюза любой OIDC-провайдер выглядит одинаково: меняется только домен эмитента.
Как только проверке нужна собственная логика, роли из базы, схемы доступа или фильтрация по происхождению запроса, приходит время Lambda-авторайзера. Для Auth0 он выглядит так:
const { createRemoteJWKSet, jwtVerify } = require("jose");
const jwks = createRemoteJWKSet(
new URL("https://dev-abc123.eu.auth0.com/.well-known/jwks.json")
);
exports.handler = async (event) => {
const token = event.headers.authorization.split(" ")[1];
try {
const { payload } = await jwtVerify(token, jwks, {
issuer: "https://dev-abc123.eu.auth0.com/",
audience: "https://api.example.com"
});
return {
isAuthorized: true,
context: { sub: payload.sub, scope: payload.scope }
};
} catch (error) {
return { isAuthorized: false };
}
};
Объект JWKS создаётся при холодном старте функции и переиспользуется между вызовами, поэтому сетевой поход за ключами случается редко. Ответ формата HTTP API предельно лаконичен: флаг isAuthorized и контекст, который шлюз передаст в бэкенд. Тот же приём работает и с Cognito, если вдруг нужна логика, которую не выражает декларативный авторайзер.
Токены и их время жизни в двух системах аутентификации
Обе системы выдают классическую OIDC-тройку: ID-токен с профилем, access-токен со скоупами и refresh-токен для продления сессии. У Cognito по умолчанию access- и ID-токены живут по 60 минут, refresh-токен 30 дней, и все три величины настраиваются на уровне клиента пула. У Auth0 время жизни настраивается для каждого приложения и API, а при обновлении refresh-токенов работает ротация: каждый новый refresh-токен заменяет предыдущий, а попытка повторно использовать уже заменённый токен помечает всю цепочку скомпрометированной и рвёт сессию. Для фронтендов это невидимо, а для безопасности такая механика закрывает целый класс атак с перехваченными токенами.
Кастомные утверждения добавляются по-разному. В Cognito за это отвечает Lambda-триггер Pre token generation, который дорабатывает полезную нагрузку токена перед выдачей. В Auth0 то же делается экшеном:
exports.onExecutePostUserLogin = async (event, api) => {
if (event.transaction.action === "signup") {
api.user.setAppMetadata("beta_tester", true);
}
api.idToken.setCustomClaim("https://api.example.com/plan", event.user.appMetadata.plan || "free");
};
Экшены пишутся на Node.js, выполняются на стороне платформы и не требуют собственного сервера, что идеально ложится на бессерверную архитектуру: логика аутентификации остаётся кодом, но живёт там же, где и учётные записи. Проверка подписи в обеих системах одинаково stateless: публичные ключи лежат в JWKS, бэкенд не ходит за каждым токеном к сервису, поэтому пики трафика не превращаются в пики нагрузки на аутентификацию.
Кастомизация от готовых страниц входа до триггеров и экшенов
Cognito даёт hosted UI: готовые страницы входа, регистрации и восстановления пароля с загрузкой логотипа и своими CSS-стилями. Быстро, но в пределах шаблона: радикально другая вёрстка потребует собственный фронтенд, который общается с API пула. Глубина кастомизации живёт в Lambda-триггерах: перехват регистрации, проверка перед аутентификацией, доработка токенов, кастомные сообщения письмом. Это знакомый любому бэкенд-разработчику путь, но каждый сценарий пишется руками.
Auth0 в этом отношении щедрее. Universal Login даёт полный контроль над страницей входа вплоть до собственного HTML и JS, при этом сохраняя защиту форм и токенов внутри платформы. Детект скомпрометированных паролей работает из коробки: платформа сверяет адреса и пароли с известными базами утечек и уведомляет или блокирует вход. Наблюдение за аномалиями тоже встроено, а экшены покрывают точки входа, регистрации и выдачи токенов. За щедрость платят кривой обучения и ограничениями: экшены работают с ограниченным набором внешних библиотек, а не с любым пакетом из реестра, и экзотическое требование иногда упирается в границы платформы, где Cognito с Lambda оставляет полную свободу.
Выбор по сценариям от прототипа до мультирегиональной платформы
Практика сводится к короткому алгоритму, и порядок шагов в нём неслучаен:
- если весь стек живёт в AWS и браузеру клиента нужен прямой доступ к S3 или DynamoDB: берите Cognito с пулом идентичности и не пишите посредника;
- если фронтенд хостится вне AWS или стек мультиоблачный: дешевле сразу взять Auth0 и не тащить AWS-зависимость только ради логина;
- если продукт обслуживает компании, а не людей: Organizations у Auth0 экономят недели разработки на приглашения, роли и раздельный брендинг;
- если бюджет жёсткий, а аудитория растёт: сравните оба прайса на своей проекции MAU, бесплатные пороги различаются в 2,5 раза;
- если аутентификация требует нестандартных проверок на каждом входе: Lambda-триггеры Cognito дают полную свободу кода, экшены Auth0 работают быстрее, но по правилам платформы;
- если важно держать все данные в собственном облаке: Cognito живёт в аккаунте AWS, а Auth0 остаётся внешним SaaS со своим хранилищем.
Два сценария заслуживают отдельных слов. Первый: переезд. Хэши паролей ни одна из систем наружу не отдаёт, поэтому миграция между провайдерами означает сброс паролей у всей базы, и это надо считать до старта, а не после. Второй: регионы. Cognito доступен во всех регионах AWS, что важно для локальных требований по размещению данных, а у Auth0 набор регионов фиксированный и ограниченный, хотя и покрывает основные рынки. Тестовые среды у Auth0 решаются дочерними тенантами, у Cognito отдельными пулами, что дешевле, но требует дисциплины в именовании.
Выбор сервиса аутентификации редко выглядит героическим решением, но он самый долгоживущий в проекте: токены, атрибуты, группы и привычки пользователей переезжают между релизами, а вот между провайдерами почти никогда. Неделя честного прототипа на обоих вариантах с реальным экраном входа и реальным вызовом API стоит дешевле, чем год подгрузки чужих костылей. Бессерверный стек тем и хорош, что аутентификация в нём вынесена наружу, и выбор этой "наружи" остаётся единственной по-настоящему архитектурной развилкой.