> ## Documentation Index
> Fetch the complete documentation index at: https://veriqa.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Быстрый старт Python

> Подключение Python-приложения к Veriqa по стандартному OpenID Connect — облако, Docker или служба ОС.

Для не-.NET стеков Veriqa — обычный OpenID Connect провайдер. Пакета Veriqa для Python нет и не
требуется: вы подключаете стандартный OIDC-клиент, а всю специфику (страница входа, QR,
подтверждение в мессенджере) Veriqa выполняет на своей стороне.

<Note>
  **Один код, любое развёртывание.** Приложению не важно, **где** работает Veriqa — в облаке Veriqa,
  в вашем контейнере Docker или службой (systemd либо служба Windows) на вашей машине. Варианты
  различаются **только адресом issuer'а** и тем, где заведён клиент; код приложения ниже одинаков
  для всех, а переключение сводится к одному `server_metadata_url`.
</Note>

## 1. Установите библиотеку

Рекомендуемая библиотека — [Authlib](https://docs.authlib.org/) (актуальная линия **1.8.x**):

```bash theme={null}
pip install authlib flask requests
```

Authlib не тянет за собой ни веб-фреймворк, ни HTTP-клиент. `flask` — фреймворк, на котором построен
пример ниже; `requests` — то, на чём построены интеграции Authlib с Flask и Django, а у Starlette и
FastAPI вместо него `httpx`, так что ставьте нужный вашему фреймворку. Без любого из этих пакетов
первый же запуск падает на блоке импортов в начале `app.py` с `ModuleNotFoundError` — ещё до
обращения к Veriqa.

Требуется **Python 3.10 или новее** — это минимум, объявленный пакетом. Готовые интеграции есть для
Flask, Django, Starlette и FastAPI; ниже показан Flask, у остальных тот же реестр `OAuth` и те же
вызовы.

## 2. Заведите клиента на стороне Veriqa

<Note>
  **Veriqa Cloud сейчас недоступен** — строки про облако описывают контракт его API. Используйте
  self-hosted; подробности — в [Veriqa Cloud](/docs/ru/cloud/overview).
</Note>

| Режим | Где заводится клиент | Что получаете |
| - | - | - |
| Облако | Раздел **Applications / OIDC clients** консоли. Вместе с проектом уже создано дефолтное приложение — **public-клиент с PKCE, без секрета и без `redirect_uri`**, так что первым делом добавьте свой `redirect_uri`. Адрес `issuer` — на экране Project settings, read-only | `issuer`, `client_id`, `redirect_uri` |
| Self-hosted | Секция `Veriqa:OpenIddict:Clients` в конфигурации сервера — см. [шаг 4 self-hosted quickstart](/docs/ru/quickstart/self-hosted#4-объявите-свой-клиент). Секция та же, как бы сервер ни был поставлен | То же, значения задаёте сами |

**У self-hosted две поставки, и для этой страницы они равнозначны.** Сервер работает в контейнере
Docker — [быстрый старт self-hosted](/docs/ru/quickstart/self-hosted) — либо ставится из архива юнитом
systemd или службой Windows, без Docker и без рантайма .NET на машине —
[установка службой ОС](/docs/ru/quickstart/os-service). Выбор определяет способ поставки сервера, а не
регистрацию клиента, адрес issuer'а и код ниже.

**Секрет не обязателен.** Минимальный рабочий набор — `issuer`, `client_id`, `redirect_uri`:
public-клиент с PKCE, и это дефолт облака. Confidential-клиент нужен, только если вы хотите
аутентифицировать серверное приложение секретом; в облаке секрет выпускается отдельным явным
действием на экране Applications и показывается **один раз**.

`redirect_uri` сверяется **точным** совпадением — wildcard не поддерживаются. Регистрируйте ровно
тот URL, который отдаёт ваш маршрут callback. Для примера на Flask ниже, запущенного как в
[шаге 7](#7-контрольная-точка), это `http://127.0.0.1:5000/authorize` — регистрируйте ровно его и
открывайте приложение в той же форме хоста (`127.0.0.1`, а не `localhost`): URL callback'а строит
`url_for(..., _external=True)` из хоста входящего запроса, а сверка на стороне Veriqa буквальная.

## 3. Зарегистрируйте провайдера

`server_metadata_url` включает discovery: Authlib сам читает
`/.well-known/openid-configuration` и находит остальные эндпоинты.

Значения код берёт из окружения, поэтому задайте их в той оболочке, из которой будете запускать
приложение. Первые три — то, что вы получили на шаге 2, четвёртая — ваша собственная:

```bash theme={null}
export VERIQA_ISSUER="https://veriqa.your-domain.com"    # адрес issuer'а
export VERIQA_CLIENT_ID="my-app"                         # заведённый клиент
export VERIQA_CLIENT_SECRET="…"                          # только для confidential-клиента
export FLASK_SECRET_KEY="$(python -c 'import secrets; print(secrets.token_hex())')"
```

`FLASK_SECRET_KEY` Veriqa не выдаёт — это ваша произвольная случайная строка, ключ, которым Flask
подписывает cookie сессии. В систему контроля версий он не попадает, и в каждом окружении значение
своё. Варианту public `VERIQA_CLIENT_SECRET` не нужен: у public-клиента секрета нет вовсе. Не задать
любую из переменных, которые читает выбранный вариант, — и первый же запуск падает с `KeyError` ещё
на импорте `app.py`, до первого маршрута.

Каждый вариант ниже — целиком «голова» `app.py`: приложение Flask, секрет сессии, нужный Authlib, и
регистрация провайдера. Маршруты [шага 4](#4-опишите-маршруты-входа) кладутся в этот же файл, ниже.

<CodeGroup>
  ```python app.py — public-клиент (PKCE) theme={null}
  import os

  from authlib.integrations.flask_client import OAuth
  from flask import Flask, redirect, session, url_for

  app = Flask(__name__)
  # Authlib хранит OAuth-состояние и PKCE code_verifier в сессии Flask,
  # поэтому секретный ключ обязателен.
  app.secret_key = os.environ["FLASK_SECRET_KEY"]

  oauth = OAuth(app)

  oauth.register(
      name="veriqa",
      client_id=os.environ["VERIQA_CLIENT_ID"],
      server_metadata_url=f"{os.environ['VERIQA_ISSUER']}/.well-known/openid-configuration",
      client_kwargs={
          "scope": "openid profile channel",
          "code_challenge_method": "S256",   # включает PKCE — задать обязательно
      },
  )
  ```

  ```python app.py — confidential-клиент theme={null}
  import os

  from authlib.integrations.flask_client import OAuth
  from flask import Flask, redirect, session, url_for

  app = Flask(__name__)
  # Authlib хранит OAuth-состояние в сессии Flask, поэтому секретный ключ обязателен.
  app.secret_key = os.environ["FLASK_SECRET_KEY"]

  oauth = OAuth(app)

  oauth.register(
      name="veriqa",
      client_id=os.environ["VERIQA_CLIENT_ID"],
      client_secret=os.environ["VERIQA_CLIENT_SECRET"],
      server_metadata_url=f"{os.environ['VERIQA_ISSUER']}/.well-known/openid-configuration",
      client_kwargs={"scope": "openid profile channel"},
  )
  ```
</CodeGroup>

<Warning>
  **PKCE в Authlib не включается сам — в отличие от способа аутентификации.** Метод аутентификации
  клиента выводится автоматически: есть `client_secret` — `client_secret_basic`, нет — `none`, то
  есть public-клиент получается просто отсутствием секрета. А вот PKCE — независимый переключатель:
  без `code_challenge_method` в `client_kwargs` public-клиент пойдёт **без** PKCE. Задавайте его
  явно.
</Warning>

Переключение между вариантами — облако, Docker, служба ОС — правка одного `VERIQA_ISSUER`.

## 4. Опишите маршруты входа

```python app.py theme={null}
@app.route("/")
def index():
    user = session.get("user")
    if user is None:
        return redirect(url_for("login"))
    return user


@app.route("/login")
def login():
    redirect_uri = url_for("authorize", _external=True)
    return oauth.veriqa.authorize_redirect(redirect_uri)


@app.route("/authorize")
def authorize():
    token = oauth.veriqa.authorize_access_token()
    userinfo = token["userinfo"]

    session["user"] = {
        "sub": userinfo["sub"],
        "name": userinfo.get("name"),
        "channel_type": userinfo.get("channel_type"),
    }
    return redirect("/")
```

`authorize_access_token()` сам обменивает код на токены и разбирает `id_token` — готовые claims
лежат в `token["userinfo"]`, отдельно вызывать userinfo-эндпоинт не нужно. `code_verifier` для
PKCE библиотека генерирует и хранит сама, руками его передавать не требуется.

Между `/login` и `/authorize` работает Veriqa: пользователь видит страницу входа с QR и списком
каналов и подтверждает вход в мессенджере на телефоне.

`/` — то, куда браузер попадает в конце: пока в сессии пусто, маршрут отправляет на `/login`, а
после успешного входа показывает, что в неё положено, — словарь, возвращённый из view Flask,
сериализуется в JSON, так что результат виден без единого шаблона.

## 5. Прочитайте claims

Базовый набор Veriqa кладёт в токены сама:

| Claim | Значение | Где |
| - | - | - |
| `sub` | Стабильный идентификатор субъекта | оба токена |
| `auth_time` | Момент завершения подтверждения (Unix) | только `id_token` |
| `amr` | Тип канала, которым подтверждён вход (`telegram`, `whatsapp`, `max`, `email`) | оба токена; в `id_token` — массивом строк (`["telegram"]`), даже при одном методе |
| `name`, `email`, `phone_number`, … | Из resolved identity — по запрошенным scopes | оба токена |
| `picture` | Аватар пользователя — только со scope `avatar`, разрешённым и в `AllowedScopes` ([подробнее](/docs/ru/concepts/oidc-explainer#resolved-identity-и-claims)) | только `access_token` — читать из userinfo |

**Claims канала выдаются только по scope `channel`:**

| Claim | Значение |
| - | - |
| `channel_type` | Тип канала, к которому привязан пользователь |
| `channel_user_id` | Идентификатор пользователя внутри этого канала |

Без запрошенного scope `channel` эти два claim не попадают **ни в один** токен — и, следовательно,
не появляются в ответе userinfo, который зеркалит access\_token. Именно по ним приложение различает
привязанные каналы, поэтому для сценария привязки аккаунта scope обязателен.

```python theme={null}
channel_type = token["userinfo"].get("channel_type")   # None, если scope channel не запрошен
```

## 6. Ограничьте вход конкретным каналом

Стандартный OIDC-параметр `acr_values` формата `channel:{тип}` сужает выбор на странице входа.
Дополнительные именованные аргументы `authorize_redirect` уходят в authorize-запрос как есть:

```python theme={null}
return oauth.veriqa.authorize_redirect(
    redirect_uri,
    acr_values="channel:telegram",
)
```

Несколько значений через пробел (`channel:telegram channel:whatsapp`) — пользователь выбирает из
перечисленных. Без параметра доступны все включённые на сервере каналы.

## 7. Контрольная точка

Запустите приложение из каталога, где лежит `app.py`, в той оболочке, где экспортированы переменные
[шага 3](#3-зарегистрируйте-провайдера):

```bash theme={null}
flask --app app run
```

Flask отдаёт его на `http://127.0.0.1:5000` — именно из этого адреса строится `redirect_uri`,
зарегистрированный на [шаге 2](#2-заведите-клиента-на-стороне-veriqa).

<Steps>
  <Step title="Проверьте discovery">
    `curl $VERIQA_ISSUER/.well-known/openid-configuration` возвращает метаданные с `https`-URL.
  </Step>

  <Step title="Запустите вход">
    Откройте `http://127.0.0.1:5000/login` — браузер должен уйти на страницу Veriqa с QR и
    каналами.
  </Step>

  <Step title="Подтвердите на телефоне">
    Подтвердите запрос в доверенном канале; браузер вернётся на ваш маршрут `/authorize`, а тот
    перенаправит его на `/`.
  </Step>

  <Step title="Проверьте claims">
    `/` показывает сессию в виде JSON: `sub` заполнен, а `channel_type` несёт канал, которым
    подтверждён вход, — он остаётся `null`, если scope `channel` не запрашивался.
  </Step>
</Steps>

## Дальше

<CardGroup cols={2}>
  <Card title="Сценарии интеграции" icon="route" href="/docs/ru/guides/scenarios">
    Привязка канала, повторное подтверждение и подтверждение действия — на уровне HTTP.
  </Card>

  <Card title="Быстрый старт: self-hosted" icon="server" href="/docs/ru/quickstart/self-hosted">
    Развернуть свой issuer в Docker.
  </Card>

  <Card title="Установка службой ОС" icon="server" href="/docs/ru/quickstart/os-service">
    Тот же issuer без Docker — systemd или служба Windows, из архива.
  </Card>

  <Card title="OIDC + Veriqa: разбор" icon="key" href="/docs/ru/concepts/oidc-explainer">
    Где Veriqa встаёт в стандартный поток.
  </Card>

  <Card title="Hardening к продакшну" icon="shield-check" href="/docs/ru/guides/hardening">
    Что проверить перед выпуском.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.