IDOC Connect — OAuth 2.0 / OIDC
IDOC Connect — OpenID Connect провайдер для аутентификации пользователей единым глобальным аккаунтом IDOC. Поддерживается только Authorization Code Flow с обязательным PKCE (RFC 7636). Идентичность возвращается в подписанном JWT (RS256); claims включают верифицированный профессиональный статус и место работы.
TTL — значения по умолчанию, могут отличаться от указанных (настраиваются на нашей стороне).
Как подключиться
В MVP нет публичной саморегистрации (Dynamic Client Registration) — каждый партнёр подключается вручную, после модерации.
- 1ЗаявкаПишете нам с описанием площадки, юр. лица и того, зачем вам конкретные поля профиля пользователя.
- 2СогласованиеПроверяем политику ПДн, согласовываем цель передачи, набор scope/claims и redirect_uri (только HTTPS, точное совпадение).
- 3ДоговорЮридическая модель отношений фиксируется договором до выдачи боевых ключей.
- 4Создание clientВыдаём client_id и одноразово показываем client_secret.
- 5Тестирование в SandboxПроверяете интеграцию на тестовых персонах (см. раздел Sandbox) — без реальных пользователей.
- 6Production activationПосле прохождения чек-листа (см. раздел ниже) client переводится в боевой режим.
Как это работает
Authorization Code Flow с PKCE. client_secret используется исключительно в back-channel обмене кода на токены и не передаётся в браузер.
- 1Редирект на /oauth2/authorizeСервер партнёра генерирует code_verifier (32 случайных байта, base64url), вычисляет code_challenge = BASE64URL(SHA256(code_verifier)) и редиректит браузер пользователя на IDOC с этими параметрами.
- 2АутентификацияIDOC проверяет активную сессию (в том числе открытый кабинет пользователя — без повторного ввода email) либо запрашивает email и magic-link.
- 3Экран согласияПри первом входе или изменении набора scope пользователю показывается список запрошенных данных, требующий подтверждения. Согласие фиксируется как Consent Receipt с временной меткой и списком scope.
- 4Возврат authorization codeБраузер редиректится на redirect_uri с параметрами code и state. Сервер партнёра обязан сверить state со значением из собственной сессии до использования code.
- 5Back-channel обмен кода на токеныСервер партнёра отправляет POST /oauth2/token с code и code_verifier. IDOC проверяет PKCE (SHA256(code_verifier) == code_challenge) и возвращает access_token, id_token, refresh_token.
- 6Валидация ID Token и создание сессииПартнёр проверяет подпись JWT по JWKS, затем claims iss, aud, exp, nonce. Локальная сессия создаётся только после успешной валидации. sub — pairwise-идентификатор, уникальный для client_id партнёра.
Эндпоинт токена
Обменивает authorization code на токены. Вызывается только с сервера партнёра; client_secret не должен присутствовать во фронтенде, логах или системе контроля версий.
client_id и client_secret передаются полями тела запроса (HTTP Basic не поддерживается). Content-Type: application/x-www-form-urlencoded. | Параметр | Обязателен | Описание |
|---|---|---|
grant_type | Да | authorization_code или refresh_token |
code | code-флоу | Код из redirect_uri |
redirect_uri | code-флоу | Должен точно совпадать со значением из /authorize |
code_verifier | PKCE | Исходный code_verifier до хеширования |
refresh_token | refresh-флоу | Ранее выданный 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"{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"token_type": "Bearer",
"expires_in": 600,
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"refresh_token": "8xLOxBtZp8jGpiz0OUaGaTJaGmC5..."
}| Поле | Тип | Описание |
|---|---|---|
access_token | string | Bearer-токен для /oauth2/userinfo |
token_type | string | Всегда Bearer |
expires_in | integer | TTL access_token в секундах (600) |
id_token | string (JWT) | Подписанный RS256 JWT с identity claims |
refresh_token | string | TTL 30 дней |
grant_type=refresh_token и refresh_token вместе с client_id/client_secret. Срок действия refresh-токена — 30 дней. Эндпоинт UserInfo
Возвращает claims аутентифицированного пользователя. Набор полей определяется выданными scope.
curl https://idoc.datacorn.ru/oauth2/userinfo \
-H "Authorization: Bearer ACCESS_TOKEN"{
"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.
| Scope | Claims | Описание |
|---|---|---|
openid | sub | Обязателен, включён по умолчанию |
profile | name, family_name, given_name, middle_name, idoc_profile_updated_at | ФИО и дата последнего обновления профиля |
email | email, email_verified | Email и статус подтверждения |
phone | phone_number, phone_number_verified | Телефон |
idoc_hcp | idoc_hcp_type, idoc_hcp_verified, idoc_specialty_primary, idoc_specialties | Профессиональный статус и специальность(и) |
idoc_location | idoc_region, idoc_city | Где практикует пользователь |
idoc_workplace | idoc_workplace_name, idoc_workplace_type, idoc_position | Место работы, его тип и должность |
idoc_verification | idoc_verification_level, idoc_verification_date | Уровень верификации и дата, когда он присвоен |
idoc_profile_sync | — | Не даёт claims сам по себе — без него не придут вебхуки profile.updated |
?error=registration_incomplete. ID-токен
Подписанный JWT (RS256, RSA-2048). Проверка подписи и claims обязательна; содержимое без валидации не считается доверенным.
{
"alg": "RS256",
"kid": "a4375...",
"typ": "JWT"
}{
"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
}sub уникально для пары (пользователь, client_id): два разных партнёра, аутентифицировавших одного пользователя, получат разные sub. Это исключает сопоставление профилей между площадками без согласия пользователя. Рекомендуется использовать iss + sub как ключ идентичности, не email. | Claim | Проверка |
|---|---|
iss | Должен быть https://idoc.datacorn.ru — точное совпадение |
aud | Должен содержать ваш client_id |
exp | Текущее время меньше exp |
nonce | Совпадает с nonce, сохранённым в сессии до редиректа |
| Подпись | RSA-SHA256 по публичному ключу из JWKS, соответствующему kid из заголовка |
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 у этого аккаунта. |
partner_account_id ↔ sub. API записи о согласии
Возвращает запись о согласии с временной меткой и списком scope — для аудита и комплаенса на стороне партнёра.
Authorization: Basic base64(client_id:client_secret), как и у /oauth2/token (не access_token и не query-параметры — секрет не должен попадать в URL, логи и историю браузера). consentId возвращается вместе с профилем при первом входе пользователя и должен быть сохранён на стороне партнёра. curl -u YOUR_CLIENT_ID:YOUR_CLIENT_SECRET "https://idoc.datacorn.ru/oauth2/consents/CONSENT_ID"{
"consent_id": "c3f1a9e2-...",
"client_id": "your_client_id",
"status": "active",
"purpose": { "code": "telemedicine_consultation", "title": "Телемедицинская консультация" },
"scopes": ["openid", "email", "idoc_hcp"],
"claims": ["sub", "email", "idoc_hcp_type", "idoc_hcp_verified"],
"document_version": "1.0",
"accepted_at": "2026-07-01T10:00:00.000Z",
"revoked_at": null,
"ledger": [
{ "event": "created", "seq": 1, "occurred_at": "2026-07-01T10:00:00.000Z", "prev_hash": null, "record_hash": "a3f5..." }
]
}purpose отсутствует в ответе (не null, а сам ключ не приходит), если на момент согласия у вашего client не была задана структурированная цель обработки — не полагайтесь на обязательное присутствие поля. ledger в ответе выше — записи из отдельного append-only журнала: каждое создание или отзыв ЭТОГО согласия зафиксировано снимком состояния и record_hash, куда входит prev_hash — hash предыдущей записи ВСЕЙ вашей цепочки согласий (не только этого consentId). Так строку в базе нельзя незаметно подправить задним числом: пересчитанный hash не совпадёт. curl -u YOUR_CLIENT_ID:YOUR_CLIENT_SECRET "https://idoc.datacorn.ru/oauth2/consents/ledger/verify"{
"intact": true,
"totalEntries": 42,
"brokenAtSeq": null,
"lastHash": "9d1c..."
}intact: false, brokenAtSeq указывает номер первой записи, на которой цепочка нарушена. Тот же результат доступен из кабинета партнёра (вкладка «Аудит», кнопка «Проверить журнал согласий») — эндпоинт нужен, только если хотите передать проверку своей юридической службе или регулятору без выдачи им доступа к кабинету. Logout и Revoke
RP-Initiated Logout завершает сессию пользователя только в рамках вашего client_id. Сессии на других площадках не затрагиваются.
| Параметр | Обязателен | Описание |
|---|---|---|
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ваш-сайт.ру%2Fcurl -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; подпись обязательна к проверке до обработки тела.
| event | Описание |
|---|---|
authorization.revoked | Врач отключил вашу площадку в своём кабинете IDOC |
hcp.verification_changed | Изменился профессиональный статус/уровень верификации пользователя |
profile.updated | Изменились согласованные с вами поля профиля |
account.deactivated | Аккаунт пользователя заблокирован |
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);
});200 на ваш вебхук-эндпоинт должен возвращаться немедленно — долгая обработка на вашей стороне до ответа увеличивает шанс попасть в 8-секундный таймаут и уйти в повтор. Ошибки
Часть ошибок authorize возвращается редиректом на redirect_uri, часть — нет. Ошибки остальных эндпоинтов — JSON в теле ответа.
client_id неизвестен, либо redirect_uri/scope не проходят валидацию, редиректа на непроверенный адрес намеренно не происходит — сервер отдаёт обычную ошибку (страницу/exception) напрямую, без ухода через redirect_uri. Это защита от open redirect. Ниже — только ошибки, которые уже прошли эту проверку и возвращаются через redirect_uri. | HTTP | Причина |
|---|---|
400 | Неизвестный или отключённый client_id |
400 | redirect_uri не зарегистрирован для этого партнёра (ни в production, ни в sandbox списке) — точное совпадение, без wildcard |
400 | Партнёр ещё в режиме тестирования — боевые ключи не выданы (redirect_uri из production-списка, но client ещё не переведён в ACTIVE) |
400 | Запрошены scope, не разрешённые этому партнёру |
400 | scope должен включать openid |
400 | claims — невалидный JSON |
400 | Не пройдена валидация запроса (см. «Формат тела ошибки» ниже) — например response_type ≠ code, code_challenge_method ≠ S256, отсутствует обязательный параметр |
| 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), но врач не отметил чекбокс — аналогично, для обычного флоу через наш экран не встречается |
| HTTP | message | Причина |
|---|---|---|
400 | unsupported_grant_type | grant_type не authorization_code и не refresh_token |
400 | invalid_request: code/redirect_uri/code_verifier обязательны | grant_type=authorization_code без одного из этих полей |
400 | invalid_request: refresh_token обязателен | grant_type=refresh_token без refresh_token |
401 | invalid_client | Неверный/отсутствующий client_id или client_secret |
400 | invalid_grant | code не найден или выдан другому client_id |
400 | invalid_grant: code уже использован | Authorization code одноразовый — второй обмен того же code |
400 | invalid_grant: code истёк | Прошло больше 90 секунд с выдачи code |
400 | invalid_grant: redirect_uri не совпадает | redirect_uri в запросе токена отличается от переданного в /authorize |
400 | invalid_grant: code_verifier не совпадает | SHA256(code_verifier) ≠ code_challenge, сохранённый при /authorize |
400 | invalid_grant: согласие не найдено | Пользователь отозвал согласие после выдачи code, до обмена на токены |
400 | invalid_grant | refresh_token не найден или выдан другому client_id |
401 | invalid_grant: обнаружено повторное использование refresh token, доступ отозван | Reuse уже провёрнутого (использованного) refresh_token — см. примечание ниже |
400 | invalid_grant: refresh token истёк | TTL refresh_token — 30 дней |
401 invalid_grant: обнаружено повторное использование refresh token… — security event: кто-то попытался использовать уже провёрнутый (ранее обменянный на новый) refresh_token. В ответ немедленно отзывается вся цепочка токенов этого пользователя у вашего client — следующий рабочий refresh_token тоже перестанет работать, пользователю нужно будет пройти Authorization Code Flow заново. | HTTP | message | Причина |
|---|---|---|
401 | invalid_token | Отсутствует заголовок Authorization: Bearer, либо access_token не проходит проверку подписи/типа/срока |
401 | invalid_token: согласие отозвано | Токен валиден, но пользователь отозвал согласие этому client уже после его выдачи |
200 {}, даже если токен не найден/уже отозван — не раскрывает партнёру, существовал ли токен вообще. Единственная ошибка — 401 invalid_client при неверном/отсутствующем client_id/client_secret. | HTTP | message | Причина |
|---|---|---|
400 | client_id обязателен (Authorization: Basic) | Не передан заголовок Authorization: Basic |
401 | invalid_client | Неверный client_id/client_secret в Basic-заголовке |
400 | consent не найден | Неверный 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 всегда строка. Безопасность
Обязательные требования протокола — часть контракта, не рекомендации.
| Требование | Значение | Правило |
|---|---|---|
| Flow | response_type=code | Implicit flow не поддерживается ни в каком виде. |
| PKCE | code_challenge_method=S256 | Обязателен для всех клиентов, включая конфиденциальных. |
| redirect_uri | Только HTTPS, exact string match | Без wildcard и без частичного совпадения по префиксу. |
| state | Обязателен, случайная строка | Сверяется с значением из вашей сессии до использования code. |
| nonce | Обязателен для OIDC | Сверяется с claim nonce внутри id_token. |
| client_secret | Только backend | Никогда не передавать во фронтенд-код, логи или систему контроля версий. |
Authorization Code | 90 секунд, одноразовый | Немедленно инвалидируется после первого использования. |
access_token | 10 минут | — |
refresh_token | до 30 дней, с ротацией | Reuse старого refresh_token — security event, вся цепочка токенов отзывается. |
client_secret | ротация с overlap 24 часа | После ротации в кабинете старый secret ещё 24 часа принимается наравне с новым — время обновить интеграцию без простоя. |
| Согласия пользователя | лимит 20 / 5 минут | На один аккаунт врача — защита от накрутки согласий; при превышении новые согласия временно не выдаются. |
| Вход в кабинет партнёра | код на email с нового IP | По умолчанию включён; при входе с незнакомого IP запрашивается одноразовый код, отправленный на email. |
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-server | Front-channel (браузер врача) никогда не несёт ФИО/email/телефон — см. раздел «Безопасность» выше | Проверьте параметры своего callback URL — там только code и state |
| Локализация данных | Серверы и база данных — в России (152-ФЗ), данные врача не покидают юрисдикцию РФ | — |
| Шифрование в транзите | Весь трафик — только HTTPS, открытого HTTP-канала для API/кабинета нет | Попробуйте обратиться по http:// — соединение не примется |
| Отключение площадки — решение врача | Врач сам видит список подключённых площадок в своём кабинете IDOC и отключает любую из них когда захочет — ни вы, ни мы не можем сделать это за него без его действия | — |
client_secret и webhook_secret хранятся так же — открытым текстом вы видите их только один раз, в момент выпуска или ротации, дальше — только хэш в базе. Если вы настроили вход по passkey — приватный ключ вообще никогда не покидает ваше устройство, у нас хранится только публичный ключ для проверки подписи. Это касается и нашей собственной команды: у сотрудников поддержки/разработки нет способа посмотреть ваш пароль или действующий client_secret в открытом виде — только хэш в базе, тот же самый, что видит и код проверки при вашем входе. Песочница
Тестовые IDOC-пользователи для проверки интеграции без реального пользователя — только на dev/стенде.
| Персона | Для чего |
|---|---|
| verified doctor / verified pharmacist | Happy path — полностью подтверждённый профиль. |
| unverified HCP | Проверка сценария с minimum_verification_level — площадка должна корректно его отклонить/понизить доступ. |
| incomplete profile | Отсутствуют специальность/регион/место работы — проверка инлайн-дозаполнения на экране согласия. |
| revoked consent | Войдите, затем отключите площадку в личном кабинете IDOC пользователя — проверка, что refresh_token перестаёт работать. |
| blocked account | authorization code не выдаётся — проверка обработки стандартной ошибки без раскрытия причины блокировки. |
POST /idoc-connect/sandbox/login требует служебный заголовок X-Idoc-Client: 1, который проставляет только наш собственный экран согласия — вызов напрямую с бэкенда вашей интеграции вернёт 403 Forbidden. Это не публичный API для тестирования из вашего кода, а внутренний эндпоинт UI: на экране согласия (/idoc-connect/consent) на стенде под формой входа есть раскрывающийся блок «Sandbox — тестовые персоны» — вход одним кликом, без реальной ссылки на почту, для быстрой ручной проверки прямо в браузере. Чтобы прогнать sandbox-флоу программно из своего теста, используйте кнопку «Проверить интеграцию» в кабинете партнёра (вкладка «Обзор») — она делает то же самое server-to-server и показывает результат каждого шага, без нужды дёргать эти два эндпоинта самостоятельно. SDK и примеры использования
Референсные реализации клиента, по одному файлу на язык — не пакеты npm/pip/composer. Скачайте файл и подключите его в проекте как обычный локальный модуль.
idocConnectClient.jsбез зависимостейidoc_connect_client.pyrequests, pyjwt[crypto]IdocConnectClient.phpext-curl, ext-opensslIdocConnectClient.csMicrosoft.IdentityModel.Tokensconst { 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');
});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 и контакты по безопасности актуальны в карточке клиента у нас