/сервис авторизации
v1.11.0OAuth 2.0 · OpenID Connect 1.0 · RFC 7636

IDOC Connect — OAuth 2.0 / OIDC

IDOC Connect — OpenID Connect провайдер для аутентификации пользователей единым глобальным аккаунтом IDOC. Поддерживается только Authorization Code Flow с обязательным PKCE (RFC 7636). Идентичность возвращается в подписанном JWT (RS256); claims включают верифицированный профессиональный статус и место работы.

Base URL
https://idoc.datacorn.ru
Discovery
/.well-known/openid-configuration
JWKS URI
/.well-known/jwks.json
Token signing
RS256 · RSA-2048
Client auth
client_secret_post
PKCE method
S256 (обязателен)
Access token TTL
600 s (10 мин)
Refresh token TTL
30 дней
Authorization code TTL
90 s · однократный

TTL — значения по умолчанию, могут отличаться от указанных (настраиваются на нашей стороне).

Как подключиться

В MVP нет публичной саморегистрации (Dynamic Client Registration) — каждый партнёр подключается вручную, после модерации.

  1. 1
    Заявка
    Пишете нам с описанием площадки, юр. лица и того, зачем вам конкретные поля профиля пользователя.
  2. 2
    Согласование
    Проверяем политику ПДн, согласовываем цель передачи, набор scope/claims и redirect_uri (только HTTPS, точное совпадение).
  3. 3
    Договор
    Юридическая модель отношений фиксируется договором до выдачи боевых ключей.
  4. 4
    Создание client
    Выдаём client_id и одноразово показываем client_secret.
  5. 5
    Тестирование в Sandbox
    Проверяете интеграцию на тестовых персонах (см. раздел Sandbox) — без реальных пользователей.
  6. 6
    Production activation
    После прохождения чек-листа (см. раздел ниже) client переводится в боевой режим.
Напишите нам в чат на странице входа партнёра (кнопка «Написать нам» внизу справа) — укажите площадку, юр. лицо, какие поля профиля нужны и зачем, и список redirect URI (только HTTPS, точное совпадение без wildcard). Ответим прямо там же, без переписки по почте.
Sandbox и production — разные redirect_uri Для client настраиваются два независимых списка redirect_uri: один для sandbox-тестирования, второй — для боевого режима. Они не пересекаются, поэтому тестовый вход не может случайно перенаправить на боевой callback и наоборот.
Статус и чек-лист в кабинете В личном кабинете партнёра сразу видно текущий статус подключения (sandbox/production, последняя активность) и автоматический чек-лист готовности к production по пунктам ниже — не нужно вычислять готовность вручную.

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

Authorization Code Flow с PKCE. client_secret используется исключительно в back-channel обмене кода на токены и не передаётся в браузер.

  1. 1
    Редирект на /oauth2/authorize
    Сервер партнёра генерирует code_verifier (32 случайных байта, base64url), вычисляет code_challenge = BASE64URL(SHA256(code_verifier)) и редиректит браузер пользователя на IDOC с этими параметрами.
  2. 2
    Аутентификация
    IDOC проверяет активную сессию (в том числе открытый кабинет пользователя — без повторного ввода email) либо запрашивает email и magic-link.
  3. 3
    Экран согласия
    При первом входе или изменении набора scope пользователю показывается список запрошенных данных, требующий подтверждения. Согласие фиксируется как Consent Receipt с временной меткой и списком scope.
  4. 4
    Возврат authorization code
    Браузер редиректится на redirect_uri с параметрами code и state. Сервер партнёра обязан сверить state со значением из собственной сессии до использования code.
  5. 5
    Back-channel обмен кода на токены
    Сервер партнёра отправляет POST /oauth2/token с code и code_verifier. IDOC проверяет PKCE (SHA256(code_verifier) == code_challenge) и возвращает access_token, id_token, refresh_token.
  6. 6
    Валидация ID Token и создание сессии
    Партнёр проверяет подпись JWT по JWKS, затем claims iss, aud, exp, nonce. Локальная сессия создаётся только после успешной валидации. sub — pairwise-идентификатор, уникальный для client_id партнёра.

Эндпоинт авторизации

Инициирует Authorization Code Flow. Требует редиректа браузера; вызов через fetch/XHR не поддерживается.

GEThttps://idoc.datacorn.ru/oauth2/authorize
Query-параметры
ПараметрОбязателенЗначение / описание
response_typeДаВсегда code
client_idДаВыдаётся при подключении площадки (через админа IDOC)
redirect_uriДаТочное совпадение с зарегистрированным — без вариантов
scopeДаЧерез пробел, включает openid; см. «Scopes и Claims»
stateДаCSRF-защита: возвращается без изменений
nonceДаФиксируется в ID Token — обязателен против replay
code_challengePKCEBASE64URL(SHA256(code_verifier))
code_challenge_methodPKCEТолько S256
Пример запроса
GET https://idoc.datacorn.ru/oauth2/authorize?
  response_type=code&
  client_id=YOUR_CLIENT_ID&
  redirect_uri=https%3A%2F%2Fваш-сайт.ру%2Fauth%2Fidoc%2Fcallback&
  scope=openid%20email%20idoc_hcp&
  state=xK2bZ9vQmR4c&
  nonce=f47ac10b-58cc-4372&
  code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
  code_challenge_method=S256
Успешный ответ: HTTP 302 → redirect_uri?code=...&state=.... Код действителен 90 секунд и предназначен для однократного использования. При отказе пользователя или незаполненных обязательных полях профиля возвращается ?error=access_denied либо ?error=registration_incomplete.

Эндпоинт токена

Обменивает authorization code на токены. Вызывается только с сервера партнёра; client_secret не должен присутствовать во фронтенде, логах или системе контроля версий.

POSThttps://idoc.datacorn.ru/oauth2/token
Аутентификация клиента: client_secret_post — client_id и client_secret передаются полями тела запроса (HTTP Basic не поддерживается). Content-Type: application/x-www-form-urlencoded.
Body-параметры
ПараметрОбязателенОписание
grant_typeДаauthorization_code или refresh_token
codecode-флоуКод из redirect_uri
redirect_uricode-флоуДолжен точно совпадать со значением из /authorize
code_verifierPKCEИсходный code_verifier до хеширования
refresh_tokenrefresh-флоуРанее выданный refresh_token
client_idДа
client_secretДа
Запрос
curl -X POST https://idoc.datacorn.ru/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE" \
  -d "redirect_uri=https://ваш-сайт.ру/auth/idoc/callback" \
  -d "code_verifier=CODE_VERIFIER" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"
Ответ (200 OK)
{
  "access_token":  "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
  "token_type":    "Bearer",
  "expires_in":    600,
  "id_token":      "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
  "refresh_token": "8xLOxBtZp8jGpiz0OUaGaTJaGmC5..."
}
ПолеТипОписание
access_tokenstringBearer-токен для /oauth2/userinfo
token_typestringВсегда Bearer
expires_inintegerTTL access_token в секундах (600)
id_tokenstring (JWT)Подписанный RS256 JWT с identity claims
refresh_tokenstringTTL 30 дней
Обновление токена (grant_type=refresh_token) Требуются grant_type=refresh_token и refresh_token вместе с client_id/client_secret. Срок действия refresh-токена — 30 дней.

Эндпоинт UserInfo

Возвращает claims аутентифицированного пользователя. Набор полей определяется выданными scope.

GEThttps://idoc.datacorn.ru/oauth2/userinfo
curl https://idoc.datacorn.ru/oauth2/userinfo \
  -H "Authorization: Bearer ACCESS_TOKEN"
Пример ответа (200 OK)
{
  "sub": "d6e6b790-0d8f-4d20-a191-ce7c3b81bc14",
  "email": "doctor@example.com",
  "email_verified": true,
  "name": "Иванов Иван Иванович",
  "idoc_hcp_type": "doctor",
  "idoc_hcp_verified": true,
  "idoc_specialty_primary": "Кардиология",
  "idoc_specialties": ["Кардиология"],
  "idoc_verification_level": "hcp_verified",
  "idoc_verification_date": "2026-08-10T09:15:00.000Z",
  "idoc_region": "Москва",
  "idoc_workplace_name": "ГКБ №1",
  "idoc_workplace_type": "hospital",
  "idoc_profile_updated_at": "2026-09-01T14:22:00.000Z"
}

Scopes и Claims

Scope перечисляются в параметре scope через пробел. Claim sub возвращается при любом запросе с openid.

ScopeClaimsОписание
openidsubОбязателен, включён по умолчанию
profilename, family_name, given_name, middle_name, idoc_profile_updated_atФИО и дата последнего обновления профиля
emailemail, email_verifiedEmail и статус подтверждения
phonephone_number, phone_number_verifiedТелефон
idoc_hcpidoc_hcp_type, idoc_hcp_verified, idoc_specialty_primary, idoc_specialtiesПрофессиональный статус и специальность(и)
idoc_locationidoc_region, idoc_cityГде практикует пользователь
idoc_workplaceidoc_workplace_name, idoc_workplace_type, idoc_positionМесто работы, его тип и должность
idoc_verificationidoc_verification_level, idoc_verification_dateУровень верификации и дата, когда он присвоен
idoc_profile_sync—Не даёт claims сам по себе — без него не придут вебхуки profile.updated
Набор scope и обязательных полей задаётся администратором IDOC при подключении клиента. Если после входа в профиле пользователя отсутствует обязательное поле, вход прерывается с ?error=registration_incomplete.

ID-токен

Подписанный JWT (RS256, RSA-2048). Проверка подписи и claims обязательна; содержимое без валидации не считается доверенным.

Header
{
  "alg": "RS256",
  "kid": "a4375...",
  "typ": "JWT"
}
Payload
{
  "iss":   "https://idoc.datacorn.ru",
  "sub":   "d6e6b790-0d8f-4d20-a191-ce7c3b81bc14",
  "aud":   "your_client_id",
  "acr":   "hcp_verified",
  "nonce": "f47ac10b-58cc",
  "auth_time": 1787854140,
  "exp":   1787854741,
  "iat":   1787854141
}
Pairwise sub Значение sub уникально для пары (пользователь, client_id): два разных партнёра, аутентифицировавших одного пользователя, получат разные sub. Это исключает сопоставление профилей между площадками без согласия пользователя. Рекомендуется использовать iss + sub как ключ идентичности, не email.
Обязательные проверки при валидации
ClaimПроверка
issДолжен быть https://idoc.datacorn.ru — точное совпадение
audДолжен содержать ваш client_id
expТекущее время меньше exp
nonceСовпадает с nonce, сохранённым в сессии до редиректа
ПодписьRSA-SHA256 по публичному ключу из JWKS, соответствующему kid из заголовка
Верификация подписи через JWKS
const crypto = require('crypto');

const { keys } = await fetch("https://idoc.datacorn.ru/.well-known/jwks.json").then(r => r.json());
const [headerB64, payloadB64, sigB64] = idToken.split('.');
const header = JSON.parse(Buffer.from(headerB64, 'base64url'));
const jwk = keys.find(k => k.kid === header.kid);

const key = crypto.createPublicKey({ key: jwk, format: 'jwk' });
const ok = crypto.verify('RSA-SHA256',
  Buffer.from(`${headerB64}.${payloadB64}`), key, Buffer.from(sigB64, 'base64url'));
if (!ok) throw new Error('подпись не совпадает');
Ротация ключей: /.well-known/jwks.json может одновременно содержать текущий и предыдущий ключ (на время overlap-периода после ротации) — всегда выбирайте ключ по kid из заголовка id_token, а не по первому элементу массива keys. Кэшировать ответ JWKS можно, но не дольше нескольких часов.

Связывание аккаунтов

Ключ идентичности — пара iss + sub, не email. Email может измениться, sub — нет.

Сценарии на вашей стороне
СценарийЧто делать
Новый пользователь (iss+sub не найден)Создаёте аккаунт, заполняете регистрационные поля из UserInfo. email/телефон считаются подтверждёнными только если в ответе пришёл соответствующий *_verified: true.
Повторный вход (iss+sub уже есть)Авторизуете существующий аккаунт, повторный пароль площадки не требуется, новый аккаунт не создаётся.
Email совпал с уже существующим аккаунтомНЕ связывайте автоматически — подтвердите владение (см. примечание ниже), затем сохраните sub у этого аккаунта.
Нельзя автоматически объединять аккаунты по совпадению email Если email из IDOC совпал с уже существующим аккаунтом на вашей площадке, покажите пользователю «На сайте уже есть аккаунт с таким email. Связать его с IDOC?» и подтвердите связывание одним из способов: активная сессия существующего аккаунта, одноразовый код на ранее подтверждённый контакт, либо иной ваш механизм подтверждения владения. Только после этого сохраняйте пару partner_account_id ↔ sub.

Logout и Revoke

RP-Initiated Logout завершает сессию пользователя только в рамках вашего client_id. Сессии на других площадках не затрагиваются.

GEThttps://idoc.datacorn.ru/oauth2/logout
ПараметрОбязателенОписание
id_token_hintНет*ID Token пользователя — по нему определяется, чью сессию отзывать. Формально не обязателен, но без него запрос ничего не отзывает и просто отвечает без ошибки — *фактически обязателен для реального выхода
post_logout_redirect_uriНетКуда редиректить после выхода — используется, только если точно совпадает с одним из зарегистрированных redirect_uri вашего client
GET https://idoc.datacorn.ru/oauth2/logout?
  id_token_hint=ID_TOKEN&
  post_logout_redirect_uri=https%3A%2F%2Fваш-сайт.ру%2F
Отзыв токена
POSThttps://idoc.datacorn.ru/oauth2/revoke
curl -X POST https://idoc.datacorn.ru/oauth2/revoke \
  -H "Content-Type: application/json" \
  -d '{"token":"REFRESH_TOKEN","client_id":"YOUR_CLIENT_ID","client_secret":"YOUR_CLIENT_SECRET"}'

Вебхуки и проверка HMAC-подписи

Уведомления о событиях доставляются POST-запросом на ваши вебхук-эндпоинты. Каждый запрос подписан HMAC-SHA256; подпись обязательна к проверке до обработки тела.

На одного client можно завести несколько вебхук-эндпоинтов (вкладка «Webhook» в кабинете партнёра) — у каждого свой URL, свой секрет и собственный набор выбранных событий. Событие отправляется только на те эндпоинты, которые на него подписаны. Журнал доставок по каждому эндпоинту виден там же.
Типы событий
eventОписание
authorization.revokedВрач отключил вашу площадку в своём кабинете IDOC
hcp.verification_changedИзменился профессиональный статус/уровень верификации пользователя
profile.updatedИзменились согласованные с вами поля профиля
account.deactivatedАккаунт пользователя заблокирован
Структура payload
POST https://ваш-сайт.ру/webhooks/idoc
Content-Type: application/json
X-Idoc-Signature: sha256=d4c14d...
X-Idoc-Event-Id: 8b3195a3-...

{
  "event_id": "8b3195a3-...",
  "event": "profile.updated",
  "created_at": "2026-08-31T10:00:00.000Z",
  "sub": "d6e6b790-...",
  "data": { "idoc_specialty_primary": "Кардиология", "idoc_region": "Москва" }
}
Проверка подписи
Заголовок X-Idoc-Signature: sha256=<hex> — HMAC-SHA256 сырого тела запроса, ключ — секрет конкретного вебхук-эндпоинта (выдаётся при создании эндпоинта, свой для каждого). Сравнение подписи должно быть timing-safe.
const crypto = require('crypto');

app.post('/webhooks/idoc', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.header('X-Idoc-Signature') || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');
  if (sig !== expected) return res.sendStatus(403);

  const event = JSON.parse(req.body.toString('utf8'));
  res.sendStatus(200);
});
Доставка: запрос отправляется асинхронно с таймаутом 8 секунд. При неуспехе (сетевая ошибка либо HTTP-ответ не 2xx) выполняется автоповтор с нарастающей паузой — 1 мин → 5 мин → 30 мин → 2 ч → 12 ч, максимум 6 попыток всего (первая + 5 повторов); после этого доставка считается окончательно неуспешной и видна в журнале с пометкой ошибки, дальше её можно только переотправить вручную из кабинета. Ответ 200 на ваш вебхук-эндпоинт должен возвращаться немедленно — долгая обработка на вашей стороне до ответа увеличивает шанс попасть в 8-секундный таймаут и уйти в повтор.
Автоотключение: если очередное событие исчерпало все 6 попыток без единого успеха, этот адрес (или конкретный дополнительный эндпоинт) помечается неактивным — дальнейшие события на него не отправляются, чтобы не копить бесполезные повторы. Статус виден в кабинете рядом с полем URL; замените адрес на рабочий и сохраните — рассылка включится снова автоматически.

Ошибки

Часть ошибок authorize возвращается редиректом на redirect_uri, часть — нет. Ошибки остальных эндпоинтов — JSON в теле ответа.

Не все ошибки authorize редиректят. Если client_id неизвестен, либо redirect_uri/scope не проходят валидацию, редиректа на непроверенный адрес намеренно не происходит — сервер отдаёт обычную ошибку (страницу/exception) напрямую, без ухода через redirect_uri. Это защита от open redirect. Ниже — только ошибки, которые уже прошли эту проверку и возвращаются через redirect_uri.
Authorize — до редиректа (обычный HTTP-ответ, не уходит на redirect_uri)
HTTPПричина
400Неизвестный или отключённый client_id
400redirect_uri не зарегистрирован для этого партнёра (ни в production, ни в sandbox списке) — точное совпадение, без wildcard
400Партнёр ещё в режиме тестирования — боевые ключи не выданы (redirect_uri из production-списка, но client ещё не переведён в ACTIVE)
400Запрошены scope, не разрешённые этому партнёру
400scope должен включать openid
400claims — невалидный JSON
400Не пройдена валидация запроса (см. «Формат тела ошибки» ниже) — например response_type ≠ code, code_challenge_method ≠ S256, отсутствует обязательный параметр
Authorize — после редиректа (?error=...&state=...)
errorПричина
access_deniedВрач отменил вход на экране согласия
registration_incompleteНе заполнены обязательные для вас поля профиля
insufficient_verification_levelУровень верификации врача ниже заданного вам minimum_verification_level — врач видит тупиковый экран и не может продолжить
processing_consent_requiredВам требуется Consent B (см. раздел «Scopes и Claims»), но врач не отметил чекбокс — теоретически возможно только при прямом вызове API в обход экрана согласия
terms_consent_requiredУ вас задан termsUrl (Consent C), но врач не отметил чекбокс — аналогично, для обычного флоу через наш экран не встречается
Token (POST /oauth2/token)
HTTPmessageПричина
400unsupported_grant_typegrant_type не authorization_code и не refresh_token
400invalid_request: code/redirect_uri/code_verifier обязательныgrant_type=authorization_code без одного из этих полей
400invalid_request: refresh_token обязателенgrant_type=refresh_token без refresh_token
401invalid_clientНеверный/отсутствующий client_id или client_secret
400invalid_grantcode не найден или выдан другому client_id
400invalid_grant: code уже использованAuthorization code одноразовый — второй обмен того же code
400invalid_grant: code истёкПрошло больше 90 секунд с выдачи code
400invalid_grant: redirect_uri не совпадаетredirect_uri в запросе токена отличается от переданного в /authorize
400invalid_grant: code_verifier не совпадаетSHA256(code_verifier) ≠ code_challenge, сохранённый при /authorize
400invalid_grant: согласие не найденоПользователь отозвал согласие после выдачи code, до обмена на токены
400invalid_grantrefresh_token не найден или выдан другому client_id
401invalid_grant: обнаружено повторное использование refresh token, доступ отозванReuse уже провёрнутого (использованного) refresh_token — см. примечание ниже
400invalid_grant: refresh token истёкTTL refresh_token — 30 дней
401 invalid_grant: обнаружено повторное использование refresh token… — security event: кто-то попытался использовать уже провёрнутый (ранее обменянный на новый) refresh_token. В ответ немедленно отзывается вся цепочка токенов этого пользователя у вашего client — следующий рабочий refresh_token тоже перестанет работать, пользователю нужно будет пройти Authorization Code Flow заново.
UserInfo (GET /oauth2/userinfo)
HTTPmessageПричина
401invalid_tokenОтсутствует заголовок Authorization: Bearer, либо access_token не проходит проверку подписи/типа/срока
401invalid_token: согласие отозваноТокен валиден, но пользователь отозвал согласие этому client уже после его выдачи
Revoke (POST /oauth2/revoke)
По RFC 7009 всегда отвечает 200 {}, даже если токен не найден/уже отозван — не раскрывает партнёру, существовал ли токен вообще. Единственная ошибка — 401 invalid_client при неверном/отсутствующем client_id/client_secret.
Consent Receipt (GET /oauth2/consents/:consentId)
HTTPmessageПричина
400client_id обязателен (Authorization: Basic)Не передан заголовок Authorization: Basic
401invalid_clientНеверный client_id/client_secret в Basic-заголовке
400consent не найденНеверный consentId, либо он принадлежит другому client_id
Формат тела ошибки
HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "statusCode": 400,
  "message": "invalid_grant",
  "error": "Bad Request"
}
Два разных формата message в одном и том же теле ответа: бизнес-ошибки (неверный код, истёкший токен и т.п.) возвращают message одной строкой; ошибки валидации самого запроса (не передан обязательный параметр, лишнее поле, неверный тип) — message массивом строк, по одной на каждое нарушение. Парсите оба варианта на своей стороне, не полагайтесь на то, что message всегда строка.

Безопасность

Обязательные требования протокола — часть контракта, не рекомендации.

ТребованиеЗначениеПравило
Flowresponse_type=codeImplicit flow не поддерживается ни в каком виде.
PKCEcode_challenge_method=S256Обязателен для всех клиентов, включая конфиденциальных.
redirect_uriТолько HTTPS, exact string matchБез wildcard и без частичного совпадения по префиксу.
stateОбязателен, случайная строкаСверяется с значением из вашей сессии до использования code.
nonceОбязателен для OIDCСверяется с claim nonce внутри id_token.
client_secretТолько backendНикогда не передавать во фронтенд-код, логи или систему контроля версий.
Authorization Code90 секунд, одноразовыйНемедленно инвалидируется после первого использования.
access_token10 минут—
refresh_tokenдо 30 дней, с ротациейReuse старого refresh_token — security event, вся цепочка токенов отзывается.
client_secretротация с overlap 24 часаПосле ротации в кабинете старый secret ещё 24 часа принимается наравне с новым — время обновить интеграцию без простоя.
Согласия пользователялимит 20 / 5 минутНа один аккаунт врача — защита от накрутки согласий; при превышении новые согласия временно не выдаются.
Вход в кабинет партнёракод на email с нового IPПо умолчанию включён; при входе с незнакомого IP запрашивается одноразовый код, отправленный на email.
Персональные данные — только через back-channel ФИО, email, телефон, специальность и другие ПДн нельзя передавать через query string callback URL, через state, через fragment URL или другие открытые browser-параметры. Front-channel передаёт только code, state и технические значения протокола — ПДн выдаются строго server-to-server через Token/UserInfo endpoints.

Модель доверия

Мы выступаем техническим посредником протокола, а не хранилищем данных врача сверх необходимого. Ниже — то, что можно проверить самостоятельно, не полагаясь на наши слова.

ГарантияКак это устроеноКак проверить самим
Claims — только по scopeВыдаётся ровно тот набор полей, что вы согласовали при подключении, не «всё, что есть»Сверьте allowed_scopes во вкладке «Обзор» кабинета с тем, что реально приходит в id_token/UserInfo
Секреты — хэшем, не строкойclient_secretПоказывается один раз при выпуске/ротации; после этого момента открытого значения нет даже у нас в базе
Журнал согласий — hash-chainЗаписи о согласии врача криптографически связаны между собойGET /oauth2/consents/ledger/verify
id_token — подписан RS256Подлинность проверяется вашим кодом, не доверием к каналу связи/.well-known/jwks.json
Отзыв согласия — мгновенныйВрач сам отключает вашу площадку в своём кабинете IDOC, без обращения к намwebhook authorization.revoked
Аудит-лог — ваш, не только нашВсе действия в кабинете (входы, смены секретов, права команды) видны вам самимВкладка «Аудит» кабинета партнёра
ПДн — только server-to-serverFront-channel (браузер врача) никогда не несёт ФИО/email/телефон — см. раздел «Безопасность» вышеПроверьте параметры своего callback URL — там только code и state
Локализация данныхСерверы и база данных — в России (152-ФЗ), данные врача не покидают юрисдикцию РФ—
Шифрование в транзитеВесь трафик — только HTTPS, открытого HTTP-канала для API/кабинета нетПопробуйте обратиться по http:// — соединение не примется
Отключение площадки — решение врачаВрач сам видит список подключённых площадок в своём кабинете IDOC и отключает любую из них когда захочет — ни вы, ни мы не можем сделать это за него без его действия—
Что мы физически не можем восстановить Ваш пароль от кабинета партнёра хранится необратимым bcrypt-хэшем — при входе сравнивается хэш, а не значение. client_secret и webhook_secret хранятся так же — открытым текстом вы видите их только один раз, в момент выпуска или ротации, дальше — только хэш в базе. Если вы настроили вход по passkey — приватный ключ вообще никогда не покидает ваше устройство, у нас хранится только публичный ключ для проверки подписи. Это касается и нашей собственной команды: у сотрудников поддержки/разработки нет способа посмотреть ваш пароль или действующий client_secret в открытом виде — только хэш в базе, тот же самый, что видит и код проверки при вашем входе.
Данные врача (ФИО, email, специальность и т.п.) выдаются вам только через официальный протокол — строго тот набор полей, что вы согласовали (раздел «Scopes и Claims»), и только после явного согласия врача на экране входа. Мы не передаём и не продаём эти данные куда-либо ещё — наша роль ограничена подтверждением личности врача и передачей согласованных claims именно вам.

Песочница

Тестовые IDOC-пользователи для проверки интеграции без реального пользователя — только на dev/стенде.

Sandbox доступен только на этапе тестирования интеграции, не в production — там эндпоинты ниже всегда отвечают 401. Используйте его до перехода на боевые ключи (см. чек-лист в конце документации).
Тестовые персоны
ПерсонаДля чего
verified doctor / verified pharmacistHappy path — полностью подтверждённый профиль.
unverified HCPПроверка сценария с minimum_verification_level — площадка должна корректно его отклонить/понизить доступ.
incomplete profileОтсутствуют специальность/регион/место работы — проверка инлайн-дозаполнения на экране согласия.
revoked consentВойдите, затем отключите площадку в личном кабинете IDOC пользователя — проверка, что refresh_token перестаёт работать.
blocked accountauthorization code не выдаётся — проверка обработки стандартной ошибки без раскрытия причины блокировки.
Эндпоинты
GEThttps://idoc.datacorn.ru/idoc-connect/sandbox/personas
POSThttps://idoc.datacorn.ru/idoc-connect/sandbox/login
Оба эндпоинта — только для нашего фронтенда, не для вашего кодаPOST /idoc-connect/sandbox/login требует служебный заголовок X-Idoc-Client: 1, который проставляет только наш собственный экран согласия — вызов напрямую с бэкенда вашей интеграции вернёт 403 Forbidden. Это не публичный API для тестирования из вашего кода, а внутренний эндпоинт UI: на экране согласия (/idoc-connect/consent) на стенде под формой входа есть раскрывающийся блок «Sandbox — тестовые персоны» — вход одним кликом, без реальной ссылки на почту, для быстрой ручной проверки прямо в браузере. Чтобы прогнать sandbox-флоу программно из своего теста, используйте кнопку «Проверить интеграцию» в кабинете партнёра (вкладка «Обзор») — она делает то же самое server-to-server и показывает результат каждого шага, без нужды дёргать эти два эндпоинта самостоятельно.

Кнопка входа — гайдлайн по бренду

Единый утверждённый вид кнопки для всех партнёров. Размер и отступы допускается адаптировать под вёрстку; текст и логотип — нет.

Тёмная
Войти через
Светлая
Войти через
ПравилоДопустимоЗапрещено
Текст«Войти через IDOC»Другой текст, смена смысла
Высота кнопкиМинимум 36 pxМеньше 36 px
ЛоготипТолько официальный SVGПерерисовка, искажение пропорций
Скругление12 pxПолностью круглая / острые углы
<a href="/auth/idoc/login" class="idoc-btn">
  <span>Войти через</span>
  <svg width="52" height="14" viewBox="0 0 332 88" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M327.942 0H272.942C270.942 0 258.842 3 252.442 9C246.042 15 240.776 25.8333 238.942 30.5C238.442 31.6667 235.342 39.3 226.942 60.5C218.542 81.7 230.109 87.6667 236.942 88H293.942C297.942 88 300.276 84.6667 300.942 83C301.276 82.1667 302.542 79.1 304.942 73.5C307.342 67.9 304.609 66.5 302.942 66.5H256.942C250.142 66.5 250.776 61.8333 251.942 59.5C255.442 51 262.942 32.8 264.942 28C266.942 23.2 271.109 22 272.942 22C285.276 21.8333 311.342 21.5 316.942 21.5C322.542 21.5 325.276 19.5 325.942 18.5C326.109 18.3333 327.142 16.2 329.942 9C332.742 1.8 329.776 0 327.942 0Z" fill="currentColor"/>
    <path d="M222.715 0C229.715 0.333333 242.415 4.5 237.215 18.5C232.015 32.5 221.049 57.6667 216.215 68.5C213.049 75 202.215 87.9 184.215 87.5C166.215 87.1 148.049 87.3333 141.215 87.5C133.382 87.3332 119.615 82.3997 127.215 64C134.815 45.6001 142.715 25.6667 145.715 18C148.049 12 157.015 0.000108393 174.215 0H222.715ZM207.715 30C211.315 22.8 204.882 21.3333 201.215 21.5H176.715C170.315 21.5001 167.049 26.8332 166.215 29.5C163.882 35.1667 158.415 48.4 155.215 56C152.015 63.5999 156.137 65.1667 158.597 65H187.215C191.215 65 194.215 60.3334 195.215 58C197.882 51.6667 204.115 37.2002 207.715 30Z" fill="currentColor"/>
    <path d="M123.463 4C127.963 4 137.163 5.80001 137.963 13C138.763 20.2 136.63 26.6667 135.463 29L117.463 74C115.13 78.6667 107.263 88 94.463 88H36.463C34.1297 87.9999 30.1631 86.3998 32.963 80C35.763 73.6 52.7963 30.3333 60.963 9.5C61.6297 7.66669 64.0631 4.00008 68.463 4H123.463ZM80.963 26C78.1631 26.0001 76.7963 28.6667 76.463 30C74.2963 36 69.163 49.7 65.963 56.5C62.7631 63.2998 65.963 64.9999 67.963 65H90.963C96.563 65 99.6296 60.6667 100.463 58.5L110.463 32.5C112.463 27.3 109.296 26 107.463 26H80.963Z" fill="currentColor"/>
    <path d="M29.7473 8.5C20.2473 32 1.24731 79.1 1.24731 79.5C-1.41946 86.3 0.802826 88 2.24731 88H19.2473C22.0473 88 25.0806 82.3333 26.2473 79.5C34.914 57.3333 52.6473 12.1 54.2473 8.5C55.8473 4.9 52.914 4 51.2473 4H37.7473C32.9473 4 30.414 7 29.7473 8.5Z" fill="currentColor"/>
  </svg>
</a>

<style>
.idoc-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 48px; padding: 0 20px; border-radius: 12px;
  background: #0A0A0D; color: #fff;
  font-family: 'Inter', ui-sans-serif, system-ui, sans-serif;
  font-weight: 500; font-style: italic; font-size: 15px;
  white-space: nowrap; text-decoration: none; border: 1px solid rgba(255, 255, 255, 0.15);
  cursor: pointer; transition: background 0.15s, transform 0.1s;
}
.idoc-btn:hover { background: #1A1A1F; }
.idoc-btn:active { transform: scale(0.98); }
</style>

SDK и примеры использования

Референсные реализации клиента, по одному файлу на язык — не пакеты npm/pip/composer. Скачайте файл и подключите его в проекте как обычный локальный модуль.

Использование
const { IdocConnectClient } = require('./idocConnectClient');

const idoc = new IdocConnectClient({
  issuer: 'https://idoc.datacorn.ru',
  clientId: process.env.IDOC_CLIENT_ID,
  clientSecret: process.env.IDOC_CLIENT_SECRET,
  redirectUri: 'https://ваш-сайт.ру/auth/idoc/callback',
});

app.get('/auth/idoc/login', (req, res) => {
  const { url, verifier, state, nonce } = idoc.createAuthorizationRequest('openid email idoc_hcp');
  req.session.idoc = { verifier, state, nonce };
  res.redirect(url);
});

app.get('/auth/idoc/callback', async (req, res) => {
  const { code, state } = req.query;
  if (state !== req.session.idoc.state) return res.status(400).send('state mismatch');
  const tokens = await idoc.exchangeCode({ code, verifier: req.session.idoc.verifier });
  const claims = await idoc.verifyIdToken(tokens.id_token, req.session.idoc.nonce);
  res.redirect('/cabinet');
});
Альтернативные библиотеки Совместима любая OIDC/OAuth 2.0 библиотека с поддержкой PKCE: openid-client (Node.js), authlib (Python), league/oauth2-client (PHP), Microsoft.IdentityModel (.NET). Конфигурация — через discovery-документ https://idoc.datacorn.ru/.well-known/openid-configuration.

Чек-лист перед production

Проверьте это до переключения client'а в боевой режим — часть пунктов зависит от юридической модели и согласовывается отдельно с нами.

  • redirect_uri — точный HTTPS без wildcard; code_verifier/state/nonce криптостойкие, client_secret только в env
  • id_token проверяется по JWKS (ключ — по kid), claims iss/aud/exp/nonce — перед созданием сессии
  • Идентичность на вашей стороне — iss + sub, не email; auto-merge аккаунтов по email не выполняется
  • Обработаны access_denied/registration_incomplete на callback, webhook проверяет HMAC timing-safe
  • Полный флоу пройден в Sandbox на всех тестовых персонах
  • Роли сторон определены, договор с IDOC подписан
  • Цель передачи ПДн и перечень полей согласованы с IDOC (без избыточных)
  • Текст согласия утверждён и отделён от пользовательского соглашения и рассылок
  • Политика обработки ПДн опубликована и доступна с экрана согласия
  • Срок хранения данных и процедура отзыва доступа определены
  • Redirect domains и контакты по безопасности актуальны в карточке клиента у нас
Юридические пункты (справа) согласовываются с командой IDOC при подключении — договор фиксирует роли, цель передачи ПДн, перечень полей, срок обработки и процедуру отзыва до того, как client переводится в production.