Регистрация с выдумыванием пароля давно стала воронкой потерь: форма, капча, письмо со ссылкой, забытый пароль через неделю. Федеративный вход решает проблему одним движением: человек нажимает кнопку знакомого провайдера, подтверждает личность там, где она уже подтверждена, и попадает в аккаунт сайта. Конверсия растёт, поддержка забывает про восстановление паролей, а пользователи перестают изобретать пароли вида один-два-три.
Под капотом этой простоты стоит связка из двух стандартов: OAuth 2.0 отвечает за разрешения и обмен кодами, OpenID Connect добавляет поверх него язык аутентификации с токеном личности. Ниже разобраны оба слоя с кодом и числами: поток авторизационного кода с защитой PKCE, проверка токена личности, построение собственной сессии, связывание учётных записей и риски, о которых молчат туториалы.
Зачем сайту чужая учётная запись и что покупает федеративный вход
Федеративный вход это делегирование подтверждения личности: сайт доверяет провайдеру идентичности проверить человека и выдаёт ему корректный ответ. Провайдером служит крупная платформа с многолетней практикой защиты учётных записей: двухфакторные подтверждения, обнаружение странного поведения, восстановление доступа. Сайт наследует этот уровень, не строя его самостоятельно.
Пользователь платит одним согласием и получает понятную механику: никаких новых паролей, никакой возни с правилами сложности, вход одним касанием на всех устройствах, где он уже вошёл к провайдеру. Люди охотно выбирают знакомую кнопку, потому что знакомый логотип обещает предсказуемость, и это обещание работает на конверсию сильнее любой формы регистрации.
Сайт получает не только снижение трения. Учтённая запись приходит с проверенным контактным ящиком, что важно для отправки писем и восстановления доступа; уменьшается база хэшей паролей, за хранение которой отвечает команда; исчезает целый класс атак на слабые пароли, потому что паролей просто нет.
Обратная сторона тоже честна: вход зависит от доступности провайдера и его политики, а разрыв связи с провайдером влечёт поддерживающие сценарии. Поэтому зрелая схема всегда оставляет резервный путь: собственный пароль или второй провайдер, и никогда не завязывает аккаунты на единственную внешнюю дверь.
OAuth 2.0 и OpenID Connect в чём разница между разрешением и входом
OAuth 2.0 изначально отвечал на другой вопрос: разрешить ли приложению доступ к чему-либо у провайдера. Классический сценарий это доступ к календарю или файлам, и в нём нет ни слова о том, кто перед приложением. Годы на вход через OAuth строили на глазок, читая почтовый ящик как доказательство личности, и эта практика родила уязвимости.
OpenID Connect закрыл пробел официальным слоем аутентификации: расширение добавляет скоуп с говорящим именем openid, токен личности в формате подписанного заявления и конечную точку с описанием профиля. Теперь вход это не интерпретация побочного эффекта, а первоклассная операция с собственной терминологией и проверками.
Токен личности это заявка в формате триплета точка-точка: заголовок, полезная нагрузка, подпись. Внутри лежат издатель, уникальный номер субъекта, аудитория, время выдачи и истечения, одноразовая метка против повторов. Подпись ставится издателем, и проверяющая сторона сверяет её по открытым ключам из опубликованного набора, который провайдер меняет ротацией.
Разделение ролей сохраняется и в терминах: сайт называется проверяющей стороной, провайдер выдает токены, и любые разрешения на чтение почты или профиля запрашиваются отдельными скоупами, если они вообще нужны. Вход требует только подтверждения личности, и умеренность в скоупах это не вежливость, а безопасность: лишние разрешения расширяют поверхность утечек.
Поток Authorization Code с PKCE шаг за шагом от редиректа до кода
Современный поток для сайтов и мобильных приложений один: авторизационный код с проверкой владения кодом. Всё начинается на странице входа: сайт генерирует две случайные строки, доказательство и его вызов, кладёт доказательство в собственную сессию и отправляет браузер к провайдеру с параметрами: идентификатор клиента, обратный вызов, скоупы, состояние против подмены, одноразовая метка, вызов кода.
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const challenge = base64url(
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))
);
Провайдер спрашивает у человека согласия, подтверждает личность своей сессией или паролем и возвращает браузер на обратный вызов с одноразовым кодом. Дальше происходит обмен за кулисами: сервер сайта звонит провайдеру по защищённому соединению, предъявляет код, доказательство и идентификатор клиента, и получает в ответ токен доступа и токен личности.
const authUrl = new URL("https://id.example.net/authorize");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", CLIENT_ID);
authUrl.searchParams.set("redirect_uri", "https://site.example.net/callback");
authUrl.searchParams.set("scope", "openid profile email");
authUrl.searchParams.set("state", stateRandom);
authUrl.searchParams.set("nonce", nonceRandom);
authUrl.searchParams.set("code_challenge", challenge);
authUrl.searchParams.set("code_challenge_method", "S256");
Защита PKCE закрывает перехват кода: без исходного доказательства перехваченный код бесполезен, и это простое дополнение сняло целый класс атак на мобильные приложения и одностраничные интерфейсы. Статус.state сверяется с сохранённым значением и отражает попытку подмены запроса, метка nonce позже проверяется внутри токена личности.
Согласие провайдера живёт недолго: код гасится за минуты и одноразово, попытка повторного обмена тем же кодом отклоняется. Токен доступа живёт дольше и служит для чтения профиля, если он нужен, токен обновления выдаётся по желанию и хранится с уважением к его силе: утечка токена обновления это долгий ключ к учётной записи.
ID токен и его проверка подписи алгоритмы и список требований
Токен личности проверяется по строгому списку, и никакая проверка не пропускается ради удобства. Подпись сверяется с открытым ключом издателя, алгоритм сверяется с ожидаемым, потому что поле алгоритма в заголовке это входные сведения, а не команда. Полезная нагрузка проходит табличный осмотр: издатель совпадает с ожидаемым, аудитория совпадает с идентификатором клиента, время истечения в будущем, время выдачи в прошлом, метка nonce равна сохранённой.
assert(payload.iss === ISSUER);
assert(payload.aud === CLIENT_ID);
assert(payload.exp > now());
assert(payload.nonce === savedNonce);
Субъект в токене это уникальный номер человека у этого провайдера, а не имя и не почтовый ящик. Номер стабилен и неизменен, и именно он служит ключом связи в базе сайта, потому что ящик человек меняет, имя редактирует, а номер остаётся. Связку провайдер и номер хранят вместе: один и тот же номер у разных провайдеров это разные люди.
Наборы ключей провайдера живут по опубликованному пути с ротацией, и проверяющая сторона обновляет их по расписанию и по требованию, когда встречает неизвестный идентификатор ключа. Кэшировать набор навечно нельзя, а запрашивать на каждый вход расточительно, и рабочая середина это кэш с проверкой срока.
Отдельная честность нужна про метод неявного возврата токена в браузер: он старой школы и исключён из современных рекомендаций. Всё, что приезжает через строку браузера, живёт в истории и логах, и токенам личности там не место.
Сессия после входа собственные куки и мост из федерации
Федерация подтверждает личность один раз, а живёт сайт в собственной сессии. После проверки токена сайт создаёт или находит аккаунт, выдаёт свою куку с идентификатором сессии, и дальше весь сайт работает с этой сессией, не беспокоя провайдера. Токены провайдера хранятся только если они нужны для чтения профиля, и в минимальной роли.
Кука сессии ставится с ограничениями: пометка только для защищённого соединения, пометка недоступности из скриптов, ограничение пути и домена, разумный срок. Сессия на сервере несёт идентификатор субъекта, время входа и метку обновления, выход уничтожает запись на сервере и куку у браузера.
Обновление сессии это отдельная политика: кто-то продлевает её при активности, кто-то спрашивает повторный вход раз в месяц, и федеративный вход делает повторный вход безболезненным, потому что кнопка провайдера работает быстрее, чем форма с паролем. Ровно поэтому строгая политика коротких сессий перестала раздражать пользователей: перелогин превратился в одно нажатие.
Синхронизация выхода между сайтом и провайдером остаётся за рамками гарантий: выход на сайте не выходит из провайдера, и наоборот. Продукт честно сообщает об этом в интерфейсе, а политики безопасности опираются на собственную сессию, а не на чужую.
Связывание учётных записей когда один человек приходит с трёх дверей
Человек нажимает кнопку Яндекса на телефоне и кнопку Google на ноутбуке, и наивный сайт создаёт ему два аккаунта. Правильная схема связывает записи по стабильному номеру субъекта и проверенному ящику: номер хранится уникальным ключом, ящик хранится с признаком подтверждённости, и совпадение любого из них предлагает объединение.
Объединение подтверждается человеком явно: сайт показывает оба аккаунта и просит подтвердить слияние, потому что автоматическое объединение по ящику без проверки опасно. Ящик без подтверждения это не доказательство, и провайдеры, выдающие ящики без их проверки, обучены быть в списке недоверенных для связывания.
Отдельный сценарий это почтовый домен: смена провайдера у компании или перенос домена меняет издателя токенов, и хранение только номера субъекта без провайдера ломает вход. Зрелая схема хранит пару: провайдер, номер, и переживает любые миграции.
Интерфейс связывания показывает пользователю его способы входа списком и позволяет добавить или отвязать лишний. Человек, который видит свои двери и управляет ими, доверяет сайту, и это доверие оплачивается годами спокойной поддержки.
Риски и защита от подбора провайдера до повторов и утечек
Первая линия защиты это строгий обратный вызов: список разрешённых значений сверяется посимвольно, и разница в пути или параметре не считается совпадением. Подменённый обратный вызов это классическая атака на поток, и лечится она не гибкостью, а жёсткостью.
Вторая линия это параметры состояния и метки: они генерируются заново на каждый вход, хранятся в защищённой сессии и сравниваются строгим равенством. Третья линия это защита кода, уже описанная выше, и её включают всегда, а не только для мобильных приложений.
Четвёртая линия это ошибки реализации: библиотеки для проверки токенов зрелые, и своими руками проверяют только подпись и список требований, всё остальное берут готовым. Пятая линия это наблюдение: нестандартные входы, отказы проверки, всплески запросов от одного источника сигналят о попытках подбора, и они заслуживают разбора, а не игнорирования.
Порядок внедрения федеративного входа свёрнут в короткую лестницу:
- выбрать двух провайдеров с полной поддержкой стандарта и зарегистрировать приложения;
- включить поток кода с проверкой владения и строгим списком обратных вызовов;
- проверять токен личности по полному списку требований с ротацией ключей;
- строить собственную сессию и хранить минимум токенов провайдера;
- связывать учётные записи по паре провайдер и номер с явным подтверждением человека;
- оставить резервный путь входа и честный выход в интерфейсе;
- наблюдать за аномалиями входа и разбирать каждый отказ проверки.
Федеративный вход это тот случай, когда безопасность и удобство не спорят, а работают в одну сторону: человек получает кнопку вместо формы, сайт получает проверенную личность без хранения паролей, а стандарты получают массовое внедрение. Дело остаётся за аккуратностью реализации, и она, как видно из разбора, сводится к короткому списку обязательных шагов, а не к искусству.