Получение первого токена
Это один полный цикл Authorization Code + PKCE против локального развёртывания, выполненный вручную. Этот поток реализует каждая клиентская библиотека, и если хотя бы раз пройти его самому, отлаживать такую библиотеку обычно становится намного проще.
В примерах предполагается ISSUER=http://localhost:3000 и публичный клиент с id demo, у которого
зарегистрирован redirect URI http://localhost:8080/callback. Подставьте свои значения со страницы
Регистрация первого клиента.
-
Сгенерируйте verifier и challenge. PKCE здесь обязателен для каждого запроса с
response_type=code, а единственный принимаемый метод —S256.// save as pkce.ts, run with: bun pkce.tsconst verifier = Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('base64url');const challenge = new Bun.CryptoHasher('sha256').update(verifier).digest('base64url');console.log({ verifier, challenge });Сохраните verifier: на шаге 4 вы отправите его на эндпоинт токенов.
-
Отправьте браузер на эндпоинт авторизации. Соберите URL со своим challenge:
http://localhost:3000/auth?response_type=code&client_id=demo&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback&scope=openid&state=8f2c1d&nonce=b41e07&code_challenge=<challenge>&code_challenge_method=S256(Всё в одну строку, без отступов.)
stateвы проверяете сами на обратном пути;nonceвозвращается в ID-токене, чтобы вы могли связать его с этим запросом. Именноscope=openidделает запрос запросом OpenID Connect и приводит к выдаче ID-токена. Для refresh-токена нужны ещёoffline_accessв scope (области доступа) иprompt=consentв этом URL, иrefresh_tokenсреди grant types клиента. У клиента, созданного в консоли, по умолчанию есть толькоauthorization_code. Если не выполнено хотя бы одно из трёх условий, scope молча отбрасывается, без ошибки: вы получаете ответ с токенами, в котором нетrefresh_token. Кроме того, сервер должен включатьoffline_accessв список поддерживаемых scope, как он и делает по умолчанию; пока это не так, эндпоинт токенов отклоняет любой refresh-токен сunsupported_grant_type. -
Войдите и дайте согласие. Сервер перенаправляет на собственные экраны взаимодействия: форму входа для бакета, на который указывает проект этого клиента, а затем экран согласия со списком scope (для нового клиента согласие включено по умолчанию). Подтверждение на обоих экранах отправляет браузер на
http://localhost:8080/callback?code=<code>&state=8f2c1d&iss=http%3A%2F%2Flocalhost%3A3000На порту 8080 ничего не обязано слушать; скопируйте
codeиз адресной строки браузера. Убедитесь, чтоstateвернулся неизменным, аiss— это ваш issuer. -
Обменяйте код на эндпоинте токенов. Код одноразовый и живёт 60 секунд, так что сделайте это сразу. Если не успеете, начните заново с шага 2:
Окно терминала curl -s http://localhost:3000/token \-H 'Content-Type: application/x-www-form-urlencoded' \-d grant_type=authorization_code \-d code=<code> \-d client_id=demo \-d redirect_uri=http://localhost:8080/callback \-d code_verifier=<verifier>redirect_uriдолжен побайтно совпадать с указанным на шаге 2 — при обмене кода сервер сравнивает его со значением, записанным вместе с кодом, и отклоняет несовпадение. Для конфиденциального клиента уберитеclient_idиз тела и вместо этого пройдите аутентификацию:-u demo:<secret>дляclient_secret_basicили-d client_id=demo -d client_secret=<secret>дляclient_secret_post.Ответ — стандартный ответ эндпоинта токенов:
{"access_token": "…","token_type": "Bearer","expires_in": 3600,"id_token": "…","scope": "openid"} -
Декодируйте ID-токен. Это подписанный JWT; полезная нагрузка — средний сегмент в base64url:
Окно терминала echo '<id_token>' | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq .Сверьте
issс вашимISSUER,aud— с id вашего клиента, аnonce— со значением из шага 2. Настоящий клиент также проверяет подпись ключом изGET /jwks, выбранным поkidтокена. Полезная нагрузка сообщает и то, как пользователь вошёл:amrравен["pwd"]для пароля или["pwd", "otp", "mfa"]с одноразовым кодом, поэтому, чтобы требовать второй фактор, проверяйте наличиеmfa, а не само наличие claim. -
Вызовите эндпоинт UserInfo с токеном доступа:
Окно терминала curl -s http://localhost:3000/userinfo -H 'Authorization: Bearer <access_token>' | jq .Что вернётся, определяется выданными scope и claims, настроенными на сервере.
Два экрана, через которые проходит шаг 3, в развёртывании по умолчанию:


Включено по умолчанию или за флагом
Заголовок раздела «Включено по умолчанию или за флагом»Большинство эндпоинтов управляется флагом, и выключенный флаг означает, что эндпоинт не
обслуживается вообще. Такой отказ намеренно неотличим от пути, которого у сервера нет. Поэтому
404 на одном из них — это ответ конфигурации, а не дефект:
- Доступны всегда, без флага:
/health,/.well-known/openid-configuration,/.well-known/security.txt,/jwks,/authи/token. Всё в этом пошаговом разборе, кроме шага 6, относится к этой группе. - Включены, пока вы их не выключите:
/userinfo(userinfo.enabled) и выход, инициированный RP (rpInitiatedLogout.enabled). - Выключены, пока вы их не включите: pushed authorization requests, интроспекция, отзыв токенов, динамическая регистрация клиентов, device flow, CIBA и управляющая плоскость MCP.
Полная таблица с флагом для каждого эндпоинта — в справочнике эндпоинтов; сами флаги — в справочнике настроек. Настройка вступает в силу, как только вы её сохраните.