# Авторизация пользователей приложения

Порядок OAuth-авторизации для приложений, работающих от лица разных пользователей портала. Пользователь входит через Битрикс24, приложение получает токен сессии и дальше обращается к API от его имени. AI-агент, которому передали ключ авторизации, проходит весь этот поток самостоятельно — по шагам ниже.

**Скоуп:** не требуется | **Базовый URL:** `https://vibecode.bitrix24.tech/v1` | **Авторизация:** `X-Api-Key: YOUR_APP_KEY`

## Когда нужна авторизация пользователя

Ключ авторизации `vibe_app_` работает от лица конкретного пользователя, установившего приложение. Чтобы платформа знала, от чьего имени обращаться к Битрикс24, к запросу прикладывается токен сессии в заголовке `Authorization: Bearer`. Токен выдаётся в конце этого потока.

Личный ключ `vibe_api_` работает от лица своего владельца и токен сессии не использует. Для фоновых сценариев без пользователя у экрана берите его, а не ключ авторизации — [Передача ключа](/docs/keys-auth#передача-ключа).

Все эндпоинты `/v1/oauth/*` отвечают по одному заголовку `X-Api-Key` с ключом авторизации, без `Authorization: Bearer`.

## Шаг 1. Отправьте пользователя на авторизацию

Сгенерируйте `state` — криптослучайную строку 16–512 символов. Это защита от подделки межсайтового запроса по RFC 6749 §10.12: значение генерирует клиент, а на возврате сверяет, что пришло то же самое. Сервер сгенерировать `state` за вас не может.

Соберите ссылку на `GET /v1/oauth/authorize` и перенаправьте на неё браузер пользователя. Это переход верхнего уровня, а не фоновый запрос — страница согласия Битрикс24 отдаёт заголовок `X-Frame-Options` и внутри `iframe` не открывается.

```javascript
const state = crypto.randomUUID()
// Сохраните state в сессии пользователя, чтобы сверить его на возврате
const url = new URL('https://vibecode.bitrix24.tech/v1/oauth/authorize')
url.searchParams.set('app_key', 'YOUR_APP_KEY')
url.searchParams.set('state', state)
url.searchParams.set('redirect_uri', 'https://myapp.example.com/callback')
window.location.href = url.toString()
```

Платформа проверит `app_key` и `redirect_uri` и перенаправит пользователя на страницу авторизации Битрикс24. После авторизации Битрикс24 вернёт пользователя на зарегистрированный обработчик приложения, а платформа — на ваш `redirect_uri`.

## Шаг 2. Примите возврат на `redirect_uri`

После авторизации платформа перенаправляет браузер на ваш `redirect_uri` с одноразовым кодом и вашим `state`:

```
https://myapp.example.com/callback?code=VIBE_AUTH_CODE&state=YOUR_STATE
```

Сверьте `state` с тем, что сохранили на шаге 1. Если значения не совпадают, прервите авторизацию. Разбор ошибок в этом адресе — [Что приходит на redirect_uri](#что-приходит-на-redirect_uri).

## Шаг 3. Обменяйте код на токен сессии

С сервера приложения обменяйте `code` на токен сессии. `redirect_uri` в теле **точно** равен тому, что был на шаге 1.

### cURL

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "app_key": "YOUR_APP_KEY",
    "code": "VIBE_AUTH_CODE",
    "redirect_uri": "https://myapp.example.com/callback"
  }'
```

### JavaScript

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    app_key: 'YOUR_APP_KEY',
    code: 'VIBE_AUTH_CODE',
    redirect_uri: 'https://myapp.example.com/callback',
  }),
})
const { access_token, user, expires_in } = await res.json()
// Сохраните access_token — он действует 24 часа
```

```json
{
  "success": true,
  "access_token": "vibe_session_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "user": { "id": "42", "name": "Иван Петров", "email": "user@example.com" },
  "expires_in": 86400
}
```

Код одноразовый и действует 5 минут.

## Шаг 4. Вызывайте API с токеном сессии

Каждый запрос к API идёт с двумя заголовками — ключ авторизации в `X-Api-Key` и токен сессии в `Authorization: Bearer`:

### cURL

```bash
curl https://vibecode.bitrix24.tech/v1/deals \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN"
```

### JavaScript

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/deals', {
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
})
const { data } = await res.json()
```

Сессия наследует права приложения на портале. Без заголовка `Authorization: Bearer` эндпоинты, которым нужен пользовательский контекст, возвращают `401 TOKEN_MISSING`.

## Что приходит на `redirect_uri`

Платформа возвращает пользователя на `redirect_uri` с результатом в параметрах запроса. Всегда приходит ваш `state` — сверяйте его первым.

| Параметр | Когда приходит | Значение |
|----------|----------------|----------|
| `code` | Авторизация прошла | Одноразовый код для [обмена на токен](#шаг-3-обменяйте-код-на-токен-сессии). Действует 5 минут |
| `state` | Всегда | Значение `state`, переданное на шаге 1 |
| `error` | Авторизация не удалась | Причина отказа — одно из значений ниже |

Значения `error`:

| `error` | Что произошло |
|---------|---------------|
| `token_exchange_failed` | Битрикс24 не выдал токены по коду авторизации |
| `invalid_domain` | Домен портала не прошёл проверку |
| `profile_fetch_failed` | Не удалось прочитать профиль пользователя на портале |

Часть отказов на приёме возврата приходит не в адресе, а JSON-ответом со статусом `400` или `500` — когда платформа ещё не знает `redirect_uri`, вернуть на него нельзя. Это `MISSING_PARAMS`, `INVALID_STATE` и `APP_CONFIG_ERROR` из таблицы [Ошибки](#ошибки).

## Правило `redirect_uri`

`redirect_uri` должен **точно** совпадать с одним из адресов, зарегистрированных у приложения — совпадают схема, хост, порт и путь. Добавить адрес возврата можно в карточке приложения в разделе [Ключи авторизации](/apps) — поле «Redirect URI (OAuth)».

У нового приложения по умолчанию зарегистрирован адрес `http://localhost` — без порта и с путём `/`. Совпадение точное, поэтому `http://localhost:3000/callback` под него не подходит. Чтобы возврат шёл на ваш адрес — `http://localhost:3000/callback` или `https://myapp.example.com/callback` — сначала добавьте его в карточке приложения. Иначе `GET /v1/oauth/authorize` вернёт `400 INVALID_REDIRECT_URI`.

Если `redirect_uri` опущен, платформа использует встроенную страницу возврата — [Авторизация без своего redirect_uri](#авторизация-без-своего-redirect_uri).

## Авторизация без своего `redirect_uri`

Когда `redirect_uri` опущен, платформа возвращает пользователя на встроенную страницу `/oauth/complete` и создаёт токен сессии на своей стороне. Приложение забирает результат опросом `GET /v1/oauth/poll` по тому же `state`.

### cURL

```bash
curl "https://vibecode.bitrix24.tech/v1/oauth/poll?app_key=YOUR_APP_KEY&state=YOUR_STATE"
```

### JavaScript

```javascript
const url = `https://vibecode.bitrix24.tech/v1/oauth/poll?app_key=YOUR_APP_KEY&state=${state}`
const res = await fetch(url)
const result = await res.json()
if (result.status === 'complete') {
  // result.access_token, result.user, result.expires_in
}
```

Пока пользователь не завершил вход, приходит статус `pending`:

```json
{ "success": true, "status": "pending" }
```

После входа тот же запрос отдаёт токен сессии. Результат одноразовый — следующий опрос по этому же `state` снова вернёт `pending`:

```json
{
  "success": true,
  "status": "complete",
  "access_token": "vibe_session_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "user": { "id": "42", "name": "Иван Петров", "email": "user@example.com" },
  "expires_in": 86400
}
```

Результат хранится 10 минут. Забирайте его сразу после возврата пользователя на страницу входа.

## Токен сессии: срок и отзыв

Токен сессии действует 24 часа и не обновляется — поле `expires_in` в ответе равно `86400`. Механизма продления нет. После истечения вызовы за пользователя возвращают `401 INVALID_SESSION` — это сигнал заново пройти авторизацию: `GET /v1/oauth/authorize` → `POST /v1/oauth/token`.

Досрочно отозвать токен — `POST /v1/oauth/revoke` с токеном сессии в заголовке `Authorization: Bearer`. Отзыв удаляет сессию пользователя, дальнейшие вызовы с этим токеном отклоняются.

### cURL

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/oauth/revoke \
  -H "Authorization: Bearer USER_SESSION_TOKEN"
```

### JavaScript

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/oauth/revoke', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer USER_SESSION_TOKEN' },
})
const { revoked } = await res.json()
```

```json
{ "success": true, "revoked": true }
```

Поле `revoked` равно `false`, если действующей сессии с таким токеном уже не было. Ответ приходит со статусом `200` в обоих случаях.

## Приложение на своём сервере

Отдельная вкладка для входа доступна не всегда — например, приложение открывается как размещение внутри Битрикс24, а страница согласия внутри `iframe` не открывается. Если приложение размещено на собственном сервере, а не за BlackHole, токен сессии текущего пользователя получают одним из двух способов — по тому, чей обработчик принимает размещение.

**Приложение Вайбкод, `appUrl` — ваш сервер.** Размещение приходит на обработчик Вайбкод, и платформа перенаправляет браузер на `<appUrl>/?code=<одноразовый код>&placement=...&member_id=...`. Ваш сервер обменивает код на токен сессии тем же запросом [`POST /v1/oauth/token`](#шаг-3-обменяйте-код-на-токен-сессии) с телом `{ app_key, code, redirect_uri }`, где `redirect_uri` точно равен настроенному `appUrl`. Токен сессии в браузер не попадает — в адресе только одноразовый код, бесполезный без `app_key`. Поле `appUrl` редактируется в карточке приложения в разделе [Ключи авторизации](/apps).

**Собственное приложение Битрикс24 со своим обработчиком.** Размещение приходит напрямую на ваш сервер с токеном пользователя Битрикс24. Обменяйте его на токен сессии серверным запросом — `POST /v1/oauth/placement-session`.

### cURL

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/oauth/placement-session \
  -H "Content-Type: application/json" \
  -d '{
    "app_key": "YOUR_APP_KEY",
    "access_token": "BITRIX24_USER_TOKEN",
    "member_id": "MEMBER_ID",
    "domain": "mycompany.bitrix24.ru"
  }'
```

### JavaScript

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/oauth/placement-session', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    app_key: 'YOUR_APP_KEY',
    access_token: 'BITRIX24_USER_TOKEN',
    member_id: 'MEMBER_ID',
    domain: 'mycompany.bitrix24.ru',
  }),
})
const { access_token, user, expires_in } = await res.json()
```

```json
{
  "success": true,
  "access_token": "vibe_session_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "user": { "id": "42", "name": "Иван Петров", "email": "user@example.com" },
  "expires_in": 86400
}
```

`domain` должен совпадать с порталом приложения. Поля `refresh_token` и `expires_in` в теле необязательны и описывают токен пользователя Битрикс24: `expires_in` — сколько он действует, по умолчанию 3600 секунд. Без действующего `refresh_token` токен Битрикс24 автоматически не обновляется — после его истечения переоткройте размещение. Сам токен сессии, как и в основном потоке, действует 24 часа.

Полный жизненный цикл встроенного приложения и примеры обработчика на Node, Python и Go — [Авторизация в приложении на BlackHole](/docs/infra/app-runtime).

## Справочник эндпоинтов

| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/v1/oauth/authorize` | Начать авторизацию — редирект на страницу входа Битрикс24 |
| GET | `/v1/oauth/callback` | Приём ответа Битрикс24. Вызывается Битрикс24, не приложением |
| POST | `/v1/oauth/token` | Обменять код на токен сессии |
| GET | `/v1/oauth/poll` | Забрать результат авторизации без своего `redirect_uri` |
| POST | `/v1/oauth/placement-session` | Токен сессии для приложения на своём сервере |
| POST | `/v1/oauth/revoke` | Отозвать токен сессии |

## Ошибки

Ключ авторизации проверяется на входе `GET /v1/oauth/authorize`, `POST /v1/oauth/token` и `POST /v1/oauth/placement-session`:

| HTTP | Код | Описание |
|------|-----|----------|
| 401 | `INVALID_APP_KEY` | `app_key` не найден или это не ключ авторизации |
| 401 | `KEY_INACTIVE` | Ключ авторизации отозван |
| 401 | `KEY_EXPIRED` | Срок действия ключа авторизации прошёл |
| 403 | `OWNER_BLOCKED` | Владелец ключа заблокирован платформой |

`GET /v1/oauth/authorize`:

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `INVALID_REQUEST` | Нет обязательного `state` или он вне диапазона 16–512 символов |
| 400 | `INVALID_REDIRECT_URI` | `redirect_uri` не зарегистрирован у приложения |
| 500 | `NO_PORTAL` | Приложение не привязано к порталу |

`GET /v1/oauth/callback` (вызывается Битрикс24):

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `MISSING_PARAMS` | Битрикс24 вернулся без `code` или `state` |
| 400 | `INVALID_STATE` | `state` не найден или истёк за 20 минут. В `error.details.reason` — `NOT_FOUND` или `EXPIRED` |
| 500 | `APP_CONFIG_ERROR` | У приложения не заданы OAuth-учётные данные |

`POST /v1/oauth/token`:

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `INVALID_REQUEST` | В теле нет `code` или `redirect_uri` |
| 400 | `INVALID_CODE` | Код не найден или принадлежит другому приложению |
| 400 | `CODE_EXPIRED` | Срок действия кода прошёл — 5 минут |
| 400 | `CODE_ALREADY_USED` | Код уже обменян на токен |
| 400 | `REDIRECT_URI_MISMATCH` | `redirect_uri` не совпадает с переданным в `GET /v1/oauth/authorize` |
| 500 | `TOKEN_NOT_FOUND` | Токен пользователя не найден |

`GET /v1/oauth/poll`:

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `INVALID_REQUEST` | `state` короче 16 символов или нет `app_key` |
| 401 | `INVALID_APP_KEY` | `app_key` не найден или это не ключ авторизации |

`POST /v1/oauth/placement-session`:

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `INVALID_REQUEST` | В теле нет `access_token`, `member_id` или `domain` |
| 400 | `DOMAIN_MISMATCH` | `domain` не совпадает с порталом приложения |
| 401 | `USER_AUTH_REQUIRED` | Токен пользователя Битрикс24 не подтвердил авторизацию на портале |
| 500 | `NO_PORTAL` | Приложение не привязано к порталу |

`POST /v1/oauth/revoke`:

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `MISSING_TOKEN` | Не передан `Authorization: Bearer` с токеном сессии |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Пример ответа при ошибке

400 — не передан обязательный `state`:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "state: Required"
  }
}
```

## Смотрите также

- [Создание и использование ключа](/docs/keys-auth)
- [Самоописание ключа](/docs/keys-auth/me)
- [Авторизация в приложении на BlackHole](/docs/infra/app-runtime)
- [Встраивание приложения в портал](/docs/keys-auth#встраивание-приложения-в-портал)
- [Ошибки](/docs/errors)
