OAuth PKCE

Авторизация пользователей и получение API-ключей через OAuth 2.0 PKCE flow

OAuth PKCE

OAuth PKCE позволяет сторонним приложениям получать API-ключи от имени пользователей usellm, без ручного создания ключей. Пользователь авторизует приложение через consent screen, выбирает организацию, scopes и лимиты расходов, после чего приложение получает готовый API-ключ.

Если вы просто хотите использовать API напрямую, создайте ключ в личном кабинете. OAuth PKCE нужен разработчикам сторонних приложений, которые запрашивают доступ у пользователей.

Как это работает

  1. Генерация PKCE: приложение генерирует пару code_verifier + code_challenge (SHA-256).
  2. Перенаправление на авторизацию: приложение перенаправляет пользователя на https://api.usellm.ru/auth/authorize с параметрами.
  3. Consent screen: пользователь видит экран подтверждения и выбирает организацию, разрешённые данные и лимит расходов.
  4. Callback с кодом: после подтверждения пользователь перенаправляется обратно на callback_url с параметром code.
  5. Обмен кода на API-ключ: приложение обменивает code + code_verifier на API-ключ через POST /auth/token.

Шаг 1: Генерация PKCE

Сгенерируйте случайный code_verifier (43-128 символов, [A-Za-z0-9\-._~]) и вычислите code_challenge как BASE64URL(SHA256(code_verifier)).

# Генерация code_verifier
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=' | tr '+/' '-_')

# Вычисление code_challenge (S256)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr -d '=' | tr '+/' '-_')

Сохраните code_verifier: он понадобится на шаге обмена кода. Никогда не передавайте его в URL или в открытом виде.

Шаг 2: Перенаправление на авторизацию

Перенаправьте пользователя на эндпоинт авторизации с необходимыми параметрами:

ПараметрОбязательныйОписание
response_typeдаВсегда code
callback_urlдаURL для редиректа после авторизации (HTTPS, исключение: localhost)
code_challengeдаBASE64URL(SHA256(code_verifier)), 43-128 символов
code_challenge_methodдаВсегда S256
stateнетПроизвольная строка для CSRF-защиты (возвращается без изменений)
app_nameнетНазвание приложения (отображается на consent screen)
# URL для перенаправления пользователя
echo "https://api.usellm.ru/auth/authorize?\
response_type=code&\
callback_url=https://example.com/callback&\
code_challenge=$CODE_CHALLENGE&\
code_challenge_method=S256&\
state=random-csrf-string&\
app_name=My+App"

Шаг 3: Обработка callback

После авторизации пользователь будет перенаправлен на ваш callback_url с параметрами:

  • При успехе: ?code=<код_авторизации>&state=<ваш_state>
  • При отказе: ?error=access_denied&state=<ваш_state>
// На вашей callback-странице
const url = new URL(window.location.href)
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')
const error = url.searchParams.get('error')

if (error) {
  console.error('Пользователь отклонил авторизацию:', error)
}
else if (code) {
  // Проверьте state для защиты от CSRF
  // Переходите к шагу 4 — обмен кода на ключ
}

Шаг 4: Обмен кода на API-ключ

Обменяйте код авторизации на API-ключ, отправив code_verifier для подтверждения PKCE:

curl -X POST "https://api.usellm.ru/auth/token" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "полученный-код",
    "code_verifier": "ваш-code-verifier",
    "callback_url": "https://example.com/callback"
  }'

Ответ:

{
  "key": "usl_abc123...",
  "user_id": "usr_123"
}

Параметр callback_url в запросе должен точно совпадать с тем, что был указан при авторизации.

Шаг 5: Использование API-ключа

Полученный ключ работает как обычный API-ключ usellm:

curl -X POST "https://api.usellm.ru/v1/chat/completions" \
  -H "Authorization: Bearer usl_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-6-astra",
    "messages": [{"role": "user", "content": "Привет!"}]
  }'

Данные пользователя

Получите информацию о пользователе, авторизовавшем ключ, через эндпоинт GET /auth/userinfo:

curl -H "Authorization: Bearer usl_abc123..." \
  "https://api.usellm.ru/auth/userinfo"

Ответ зависит от scopes, разрешённых пользователем:

{
  "user_id": "usr_123",
  "email": "user@example.com",
  "organization": {
    "id": "org_456",
    "name": "Моя организация"
  }
}