OAuth без client secret

Быстрый старт

Канонический issuer — https://moddingflow.com. Desktop, mobile, CLI и Mod Manager используют публичный client_id с token_endpoint_auth_method=none и никогда не получают и не отправляют статический client_secret.

Приложение автоматически регистрирует клиент один раз при первом входе. Для каждой авторизации создавайте новые verifier, S256 challenge, state и nonce, открывайте системный браузер, проверяйте state и iss, затем меняйте code на токены с client_id и исходным verifier. Пользователь нажимает кнопку входа и не копирует client_id или токены.

Отправьте POST https://moddingflow.com/oauth/register с Content-Type: application/json и минимальным телом {"client_name":"My Mod Manager","redirect_uris":["http://127.0.0.1/callback"]}. Developer account, contact metadata, ручное одобрение и client secret не нужны; базовые scopes назначаются автоматически.

Ответ содержит client_id, registration_client_uri и один раз registration_access_token, но не client_secret. Приложение автоматически сохраняет публичный client_id. Management token отбрасывается, если регистрацией больше не управляют, либо остаётся только в защищённом product backend; пользователь его не видит.

Authorization Code + PKCE

Создайте 32 случайных байта системным CSPRNG, закодируйте verifier как base64url без padding и передайте code_challenge=BASE64URL(SHA256(verifier)) вместе с code_challenge_method=S256. Независимо создайте state и OpenID Connect nonce.

Откройте GET https://moddingflow.com/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK&scope=openid%20mods:read&code_challenge=CHALLENGE&code_challenge_method=S256&state=STATE&nonce=NONCE в системном браузере. Embedded webview, implicit flow, password grant и prompt=none не поддерживаются.

В успешном и ошибочном callback требуйте точные state и iss=https://moddingflow.com. Пятиминутный code обменивается через POST /oauth/token с grant_type=authorization_code, client_id, code, redirect_uri и code_verifier, без client secret.

OIDC ID token проверяйте через discovery и JWKS: подпись RS256, issuer, audience, expiry и исходный nonce. Access token действует 15 минут.

Device Flow

Отправьте POST /oauth/device_authorization с client_id и scope, покажите verification_uri_complete и user_code вместе с полным описанием доверия и прав. Опрос POST /oauth/token с Device Grant выполняйте не чаще возвращённого interval.

Точно обрабатывайте authorization_pending, slow_down, access_denied и expired_token. Прекращайте опрос после отказа, истечения или успеха и не записывайте device_code и токены в логи.

Регистрация и callbacks

registration_access_token имеет префикс mfrg_ и является административным credential регистрации, а не OAuth access/refresh token. Если lifecycle регистрации не нужен, сразу отбросьте его. Иначе храните только в защищённом product backend или secret manager и передавайте в SDK отдельным аргументом managementToken. Никогда не показывайте token пользователю и не просите его копировать.

GET registration_client_uri читает metadata без показа и ротации токена. PUT заменяет metadata и атомарно ротирует токен. DELETE отключает клиент и отзывает grants. Claim навсегда аннулирует management token.

Новые клиенты могут использовать точный HTTPS callback, loopback IP 127.0.0.1 или [::1] с динамическим портом либо reverse-domain схему вроде com.example.manager:/oauth/callback. localhost, fragments, userinfo, wildcard hosts и произвольные схемы запрещены.

Scopes, consent и проверка

Анонимная регистрация может запросить подмножество openid, mods:read, files:download, install_plans:resolve и profile:read. Без scope выбирается весь базовый набор. Consent показывается при каждой авторизации, даже если права уже выдавались.

Для mods:write, управления версиями и файлами, webhooks, private data или team administration привяжите динамический клиент в Developer Settings по client_id и management token. Claim требует same-origin сессию и AAL2/2FA.

Чувствительный запрос проходит состояния pending, approved или rejected; решение администратора требует причины и записывается в аудит. rejected закрывает доступ. agent:* и admin:* всегда запрещены внешним приложениям.

Токены, отзыв и ошибки

Refresh token ротируется при каждом использовании, а повтор старого отзывает всю семью. POST /oauth/revoke идемпотентен. Отзыв приложения в Account Security закрывает consent, refresh families, ожидающие codes и device grants, а текущие Bearer tokens сразу перестают работать.

invalid_request, invalid_client, invalid_grant, invalid_scope, unauthorized_client, access_denied и interaction_required завершают текущую попытку. Повторяйте только документированные временные ошибки и соблюдайте Retry-After с jitter.

Открытая регистрация ограничена телом 16 KiB, 10 запросами с IP в час и 50 в день. Management, claim, device polling и ошибки аутентификации используют отдельные shared fail-closed buckets.

SDK helpers

TypeScript: const request = await createOAuthAuthorizationRequest({ clientId, redirectUri, scopes: ["openid", "mods:read"] }); сохраните request.verifier, request.state и request.nonce, затем откройте request.authorizationUrl.

Python: request = create_oauth_authorization_request(client_id=client_id, redirect_uri=redirect_uri, scopes=["openid", "mods:read"]); сохраните verifier, state и nonce, затем откройте authorization_url.

C#: var request = ModdingflowPublicApiClient.CreateOAuthAuthorizationRequest(clientId, redirectUri, new[] { "openid", "mods:read" }); сохраните Verifier, State и Nonce, затем откройте AuthorizationUrl.