Цифровой профиль ФЛ: все виды сведений и реализация

ESIA-Adapter : 8.7. Цифровой профиль ФЛ: все виды сведений и реализация


Назначение

Цифровой профиль физического лица — сведения о гражданине из государственных информационных систем (МВД, ЗАГС, СФР, ФНС, Росреестр и других), доступные через ЕСИА по согласию пользователя. ESIA Adapter получает эти сведения во время входа и передаёт их приложению в атрибутах пользователя.

Для получения сведений через адаптер в файле конфигурации внешнего провайдера (extidp.json) задаются три вещи:

  1. параметр permissions в redirectForm — какие согласия пользователь даёт информационной системе;
  2. шаги запросов к REST API Цифрового профиля в userInfoPipeline;
  3. правила передачи полученных значений в атрибуты пользователя в attrMapping.

Изменений в самом адаптере не требуется. Готовые примеры по видам сведений — на страницах источников (8.7.1–8.7.5) и в подробных кейсах.

Принцип работы

  1. Адаптер направляет пользователя в ЕСИА на /aas/oauth2/v2/ac со scope=openid и параметром permissions — JSON-массив согласий в URL-safe Base64.
  2. Пользователь подтверждает согласие в ЕСИА.
  3. Адаптер обменивает авторизационный код на токен на /aas/oauth2/v3/te (без permissions). Маркер доступа содержит информацию о согласии.
  4. Адаптер выполняет шаги конвейера: запрашивает сведения методом GET /digital/api/public/{version}/pso/{oid}/doc/{doc_type} с маркером в заголовке Authorization.
  5. Адаптер преобразует ответы в атрибуты пользователя и возвращает их приложению.

Для API ЕСИА v2 используется шаблон extidp.esia.v2.pkce.example.json из комплекта поставки, провайдер криптографии — cprocsp_raw.

Параметр permissions

Перед кодированием — JSON-массив:

[
  {
    "sysname": "<ТИП_СОГЛАСИЯ>",
    "scopes": [ { "sysname": "history_passport_doc" } ],
    "purposes": [ { "sysname": "<ЦЕЛЬ>" } ],
    "actions": [ { "sysname": "ALL_ACTIONS_TO_DATA" } ],
    "responsibleObject": "<ФИО_ИЛИ_ПОДРАЗДЕЛЕНИЕ>",
    "expire": 262800
  }
]
ПолеЗначение
sysnameмнемоника типа согласия из реестров платформы согласий
scopesобласти доступа, запрашиваемые в рамках согласия
purposesцели из реестра целей
actionsдопустим только ALL_ACTIONS_TO_DATA
expireсрок действия в минутах (262800 — 182,5 суток), не больше максимума для типа согласия; предельное значение для всех типов — 26 297 460 минут
responsibleObjectответственное лицо или иные сведения (необязательно)
ogrnОГРН организации, от имени которой запрашивается согласие, целое число (необязательно)
terms, sitesусловия обработки и ресурсы оператора; применяются только к согласию на распространение данных (необязательно)

В запросе авторизации в scope остаётся только openid. В файле конфигурации значение можно задать шаблоном: функция base64 адаптера кодирует URL-safe без символов =:

"permissions": "{{ $perm := printf `[{\"sysname\":\"<ТИП_СОГЛАСИЯ>\",\"scopes\":[{\"sysname\":\"history_passport_doc\"}],\"purposes\":[{\"sysname\":\"<ЦЕЛЬ>\"}],\"actions\":[{\"sysname\":\"ALL_ACTIONS_TO_DATA\"}],\"expire\":262800}]` }}{{ $perm | base64 }}"

Согласие запрашивается только у пользователей с подтверждённой учётной записью и, по умолчанию, старше 18 лет (возраст задаётся в настройках типа согласия). Если пользователь не подходит под условия, ЕСИА выдаёт токен с областью openid без запроса согласия; запросы сведений для такого пользователя возвращают NO_CONSENT. Пользователь может отклонить необязательные области доступа типа согласия: сведения по ним недоступны. Если согласие с теми же типом, целями, действиями и областями уже выдано и не истекло, экран согласий не показывается. При обмене кода на токен в scope также указывается только openid.

Шаг запроса сведений

{ "t": 0, "r": "gibdd", "m": "GET",
  "u": "{{.extidp_details.baseUrl}}/digital/api/public/v1/pso/{{printf \"%.0f\" .id_token_claims.sub}}/doc/GIBDD_DRIVER_LICENSE",
  "h": { "Authorization": "Bearer {{.token.access_token}}" } }

Для ответов-массивов параметр r (имя переменной результата) обязателен.

Вариант с идентификатором согласия

Коммерческие методы /digital/api/public/commercial/{version}/pso/{oid}/doc/{doc_type} принимают идентификатор согласия в параметре permissionId. Идентификатор согласия (id) можно получить из списка согласий, выданных организации: поле permissions_url маркера доступа содержит адрес метода /esia-rs/api/public/{version}/prns/{oid}/issued/permissions. Для каждого согласия в ответе указаны id, status (A — выдано, D — отозвано), expiredOn и scopes; в примере выбирается первое действующее согласие (status = A). Идентификатор также передаётся в уведомлениях платформы согласий (issPrmId).

{ "t": 2, "d": { "at_claims": "jwt(token.access_token)" } },
{ "t": 0, "r": "consents", "m": "GET", "u": "{{.at_claims.permissions_url}}",
  "h": { "Authorization": "Bearer {{.token.access_token}}" } },
{ "t": 2, "d": { "permission_id": "r = \"\"; if (consents && consents.elements) { for (i = 0; i < consents.elements.length; i++) { c = consents.elements[i]; if (c.status === \"A\") { r = String(c.id); break; } } } r;" } },
{ "t": 0, "r": "gibdd", "m": "GET",
  "u": "{{.extidp_details.baseUrl}}/digital/api/public/commercial/v1/pso/{{printf \"%.0f\" .id_token_claims.sub}}/doc/GIBDD_DRIVER_LICENSE/raw",
  "d": { "permissionId": "{{.permission_id}}" },
  "h": { "Authorization": "Bearer {{.token.access_token}}" } }

Состояние ответа

Ведомственные сведения запрашиваются асинхронно, поэтому ответ может не содержать данных. Примеры определяют состояние JavaScript-шагом и передают его приложению в атрибуте:

СостояниеПризнак в ответеДействие приложения
yesесть statusиспользовать сведения
pendingтолько oid и requestIdзапрос ушёл в ведомство; повторить позже
NO_CONSENTstatusDoc: NO_CONSENTзапросить согласие пользователя
errorполе errorданных нет или ошибка ведомства
noиноесведений нет

Особенности

  • Любой ответ, кроме 2xx (например, 403 «доступ к REST API ограничен»), прерывает вход пользователя. Заявку на снятие ограничения подаёт организация.
  • Адаптер выполняет конвейер один раз за вход и не ожидает ответа ведомства: данные по requestId приложение получает при следующем входе.
  • Ответ с пустым телом или с пробельным символом перед [ обрабатывается как ошибка; ответ с Content-Type, отличным от application/json, сохраняется строкой.
  • В атрибуты (attrMapping.x) передаются только строки. Содержимое документов (XML, PDF) в атрибуты не передаётся; рекомендуется передавать признак наличия, год или количество.
  • Если значения нет, шаблон {{.person.firstName}} вернёт <no value>, а при отсутствии переменной person вход прервётся. Для безопасного обращения применяется {{ with .person.firstName }}{{ . }}{{ end }} или, в JavaScript, || "".
  • JavaScript-шаги выполняются движком ES5 (без let, стрелочных функций, шаблонных строк) не дольше 10 секунд.
  • Каждый запрос документа добавляет один HTTP-запрос к ЕСИА на вход. Данные пользователя кэшируются на время USERINFO_TTL.

Источники сведений

Виды сведений сгруппированы по ведомствам; на каждой странице — таблица видов с doc_type и областями доступа, права, шаги запросов, обработка ответа и атрибуты.

ИсточникВиды сведенийСтраница
МВДпаспорт РФ, паспорта детей, загранпаспорт, документ иностранного гражданина, история паспортов, водительское удостоверение, выписка о ТС8.7.1
ЗАГСсвидетельства о рождении (РФ, СССР, другая страна, ребёнок), о браке, расторжении брака, перемене имени, отцовстве8.7.2
СФРИЛС, пенсии и выплаты, меры соцзащиты, предпенсионный возраст, трудовая деятельность, СНИЛС детей, свидетельство пенсионера, инвалидность, многодетная семья, материнский капитал8.7.3
ФНС2-НДФЛ, доходы и взносы, ЕГРИП, ЕГРЮЛ, самозанятые, НПД, ИНН детей8.7.4
Росреестр и другиенедвижимость, полис ОМС (в том числе ребёнка), студенческий билет, удостоверение ГГС, сведения об учителе8.7.5

Базовые сведения

Базовые сведения (GET /digital/api/public/{version}/pso/{oid}) передаются по областям доступа:

СведениеАтрибутОбласть доступа
ФИОfirstName, middleName, lastNamefullname
СНИЛСsnilssnils
ИННinninn
Пол, дата и место рожденияgender, birthDate, birthPlacegender, birthdate, birthplace
Гражданствоcitizenshipcitizenship
Email, телефонemail, mobilePhoneemail, mobile
Адреса регистрации и проживанияregistrationAddress, homeAddressaddresses

Версии методов меняются: ИЛС и трудовая деятельность — с 10.10.2026, пенсии и предпенсионный возраст — с 01.12.2026. Актуальная версия метода указывается на странице источника.

Добавление нового вида сведений

  1. Описание вида сведений находится на info.gosuslugi.ru, в статье «получить сведения … через REST API с идентификатором согласия»: в ней указаны doc_type, версия API и формат ответа.
  2. Область доступа добавляется в scopes параметра permissions.
  3. Добавляется шаг запроса (см. «Шаг запроса сведений»).
  4. Добавляется JavaScript-шаг состояния ответа по образцу страниц источников.
  5. Нужные поля добавляются в attrMapping.x.
  6. Вход проверяется на тестовом контуре ЕСИА (https://esia-portal1.test.gosuslugi.ru); для методов, где он предусмотрен (например, PASSPORT_HISTORY), тестовые ответы запрашиваются параметром test=N.

Полный пример конфигурации

Файл конфигурации внешнего провайдера (extidp.json) целиком на примере сведений МВД. Значение typeDetails — строка с JSON; значения в <...> задаются при настройке. Для других источников добавляются области доступа и шаги со страниц 8.7.1–8.7.5.

[
  {
    "id": "<UUID_ПРОВАЙДЕРА>",
    "redirectMethod": 0,
    "authorizeUri": "{{.extidp_details.baseUrl}}/aas/oauth2/v2/ac",
    "logoutRedirectMethod": 0,
    "logoutUri": "{{.extidp_details.baseUrl}}/aas/oauth2/api/logout",
    "logoutForm": {
      "redirect_url": "https://<адрес-после-выхода>",
      "client_id": "{{ .extidp_details.clientID }}"
    },
    "type": "esia",
    "name": "dp-mvd",
    "synonym": "esia",
    "typeDetails": "{\n  \"baseUrl\": \"https://esia-portal1.test.gosuslugi.ru\",\n  \"clientCertHash\": \"<ХЕШ_СЕРТИФИКАТА_ГОСТ_Р_34.11-2012_256>\",\n  \"clientID\": \"<MNEMONIC_ИС_В_ЕСИА>\",\n  \"cryptoProvider\": {\n    \"name\": \"cprocsp_raw\",\n    \"details\": {\n      \"certPath\": \"/opt/esia-adapter/certs/<сертификат_ИС>.cer\",\n      \"containerName\": \"<ИМЯ_КОНТЕЙНЕРА>\",\n      \"containerPIN\": \"<PIN_КОНТЕЙНЕРА>\",\n      \"sha1Thumbprint\": \"<SHA1_ОТПЕЧАТОК>\",\n      \"hashAlg\": \"GOST3411_2012_256\"\n    }\n  },\n  \"scope\": \"openid\",\n  \"scopeOrg\": \"\"\n}",
    "redirectForm": {
      "response_type": "code",
      "client_id": "{{ .extidp_details.clientID }}",
      "redirect_uri": "{{ .extidp_request.CallbackURI }}",
      "scope": "{{ .extidp_details.scope }}",
      "client_secret": "{{ $ts := timeFormat \"2006.01.02 15:04:05 Z0700\" .process_started }}{{ $clientSecretRaw := join .extidp_details.clientID .extidp_details.scope $ts .extidp_request.ID .extidp_request.CallbackURI }}{{ cryptoSign .context $clientSecretRaw .extidp_details.cryptoProvider | base64 }}",
      "client_certificate_hash": "{{ .extidp_details.clientCertHash }}",
      "timestamp": "{{ timeFormat \"2006.01.02 15:04:05 Z0700\" .process_started }}",
      "state": "{{ .extidp_request.ID }}",
      "access_type": "offline",
      "code_challenge_method": "{{ .extidp_details.cryptoProvider.details.hashAlg }}",
      "code_challenge": "{{ cryptoHashSum .context .code_verifier .extidp_details.cryptoProvider }}",
      "permissions": "{{ $perm := printf `[{\"sysname\":\"<ТИП_СОГЛАСИЯ>\",\"scopes\":[{\"sysname\":\"id_doc\"},{\"sysname\":\"kid_id_doc\"},{\"sysname\":\"foreign_passport_doc\"},{\"sysname\":\"history_passport_doc\"},{\"sysname\":\"drivers_licence_doc\"},{\"sysname\":\"vehicle_reg_cert_doc\"}],\"purposes\":[{\"sysname\":\"<ЦЕЛЬ>\"}],\"actions\":[{\"sysname\":\"ALL_ACTIONS_TO_DATA\"}],\"responsibleObject\":\"<ФИО_ИЛИ_ПОДРАЗДЕЛЕНИЕ>\",\"expire\":<СРОК_В_МИНУТАХ>}]` }}{{ $perm | base64 }}"
    },
    "userInfoPipeline": [
      {
        "t": 1,
        "d": {
          "state": "{{ guid }}"
        }
      },
      {
        "t": 0,
        "r": "token",
        "m": "POST",
        "u": "{{.extidp_details.baseUrl}}/aas/oauth2/v3/te",
        "d": {
          "client_certificate_hash": "{{.extidp_details.clientCertHash}}",
          "client_id": "{{.extidp_details.clientID}}",
          "client_secret": "{{$ts := timeFormat \"2006.01.02 15:04:05 Z0700\" .process_started}}{{$clientSecretRaw := join .extidp_details.clientID .extidp_details.scope $ts .state .extidp_request.CallbackURI .code}}{{ cryptoSign .context $clientSecretRaw .extidp_details.cryptoProvider | base64 }}",
          "code": "{{.code}}",
          "grant_type": "authorization_code",
          "redirect_uri": "{{.extidp_request.CallbackURI}}",
          "scope": "{{.extidp_details.scope}}",
          "state": "{{.state}}",
          "timestamp": "{{timeFormat \"2006.01.02 15:04:05 Z0700\" .process_started}}",
          "token_type": "Bearer",
          "code_verifier": "{{ .code_verifier }}"
        }
      },
      {
        "t": 1,
        "d": {
          "verify_id_token": "{{ cryptoVerifyJWT .context .token.id_token .extidp_details.cryptoProvider }}"
        }
      },
      {
        "t": 2,
        "d": {
          "id_token_claims": "jwt(token.id_token)"
        }
      },
      {
        "t": 1,
        "d": {
          "is_trusted": "{{ with index .id_token_claims \"urn:esia:sbj\" \"urn:esia:sbj:is_tru\" }}{{ if not . }}{{ fail \"user is not trusted\" }}{{ end }}{{ end }}"
        }
      },
      {
        "t": 0,
        "r": "person",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/digital/api/public/v1/pso/{{printf \"%.0f\" .id_token_claims.sub}}",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        }
      },
      {
        "t": 1,
        "d": {
          "person_ok": "{{ with .person.oid }}{{ else }}{{ fail \"cp: pso response has no oid\" }}{{ end }}"
        }
      },
      {
        "t": 0,
        "r": "rf_passport",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/digital/api/public/v1/pso/{{printf \"%.0f\" .id_token_claims.sub}}/doc/RF_PASSPORT",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        }
      },
      {
        "t": 0,
        "r": "kid_rf_passport",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/digital/api/public/v1/pso/{{printf \"%.0f\" .id_token_claims.sub}}/doc/KID_RF_PASSPORT",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        }
      },
      {
        "t": 0,
        "r": "frgn_pass",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/digital/api/public/v1/pso/{{printf \"%.0f\" .id_token_claims.sub}}/doc/FRGN_PASS",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        }
      },
      {
        "t": 0,
        "r": "fid_doc",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/digital/api/public/v1/pso/{{printf \"%.0f\" .id_token_claims.sub}}/doc/FID_DOC",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        }
      },
      {
        "t": 0,
        "r": "passport_history",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/digital/api/public/v2/pso/{{printf \"%.0f\" .id_token_claims.sub}}/doc/PASSPORT_HISTORY",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        }
      },
      {
        "t": 0,
        "r": "gibdd_driver_license",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/digital/api/public/v1/pso/{{printf \"%.0f\" .id_token_claims.sub}}/doc/GIBDD_DRIVER_LICENSE",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        }
      },
      {
        "t": 0,
        "r": "vehicle_info",
        "m": "GET",
        "u": "{{.extidp_details.baseUrl}}/digital/api/public/v1/pso/{{printf \"%.0f\" .id_token_claims.sub}}/doc/VEHICLE_INFO",
        "h": {
          "Authorization": "Bearer {{.token.access_token}}"
        }
      },
      {
        "t": 2,
        "d": {
          "rf_passport_status": "r = \"no\"; d = rf_passport; if (Array.isArray(d)) { if (d.length && d[0].error) { r = \"error\"; } else if (d.length && d[0].status) { r = \"yes\"; } else if (d.length && d[0].requestId) { r = \"pending\"; } else if (d.length && d[0].statusDoc) { r = d[0].statusDoc; } } else if (d) { if (d.statusDoc) { r = d.statusDoc; } else if (d.requestId) { r = \"pending\"; } else if (d.error) { r = \"error\"; } else if (d.status) { r = \"yes\"; } } r;",
          "kid_rf_passport_status": "r = \"no\"; d = kid_rf_passport; if (Array.isArray(d)) { if (d.length && d[0].error) { r = \"error\"; } else if (d.length && d[0].status) { r = \"yes\"; } else if (d.length && d[0].requestId) { r = \"pending\"; } else if (d.length && d[0].statusDoc) { r = d[0].statusDoc; } } else if (d) { if (d.statusDoc) { r = d.statusDoc; } else if (d.requestId) { r = \"pending\"; } else if (d.error) { r = \"error\"; } else if (d.status) { r = \"yes\"; } } r;",
          "frgn_pass_status": "r = \"no\"; d = frgn_pass; if (Array.isArray(d)) { if (d.length && d[0].error) { r = \"error\"; } else if (d.length && d[0].status) { r = \"yes\"; } else if (d.length && d[0].requestId) { r = \"pending\"; } else if (d.length && d[0].statusDoc) { r = d[0].statusDoc; } } else if (d) { if (d.statusDoc) { r = d.statusDoc; } else if (d.requestId) { r = \"pending\"; } else if (d.error) { r = \"error\"; } else if (d.status) { r = \"yes\"; } } r;",
          "fid_doc_status": "r = \"no\"; d = fid_doc; if (Array.isArray(d)) { if (d.length && d[0].error) { r = \"error\"; } else if (d.length && d[0].status) { r = \"yes\"; } else if (d.length && d[0].requestId) { r = \"pending\"; } else if (d.length && d[0].statusDoc) { r = d[0].statusDoc; } } else if (d) { if (d.statusDoc) { r = d.statusDoc; } else if (d.requestId) { r = \"pending\"; } else if (d.error) { r = \"error\"; } else if (d.status) { r = \"yes\"; } } r;",
          "passport_history_status": "r = \"no\"; d = passport_history; if (Array.isArray(d)) { if (d.length && d[0].error) { r = \"error\"; } else if (d.length && d[0].status) { r = \"yes\"; } else if (d.length && d[0].requestId) { r = \"pending\"; } else if (d.length && d[0].statusDoc) { r = d[0].statusDoc; } } else if (d) { if (d.statusDoc) { r = d.statusDoc; } else if (d.requestId) { r = \"pending\"; } else if (d.error) { r = \"error\"; } else if (d.status) { r = \"yes\"; } } r;",
          "gibdd_driver_license_status": "r = \"no\"; d = gibdd_driver_license; if (Array.isArray(d)) { if (d.length && d[0].error) { r = \"error\"; } else if (d.length && d[0].status) { r = \"yes\"; } else if (d.length && d[0].requestId) { r = \"pending\"; } else if (d.length && d[0].statusDoc) { r = d[0].statusDoc; } } else if (d) { if (d.statusDoc) { r = d.statusDoc; } else if (d.requestId) { r = \"pending\"; } else if (d.error) { r = \"error\"; } else if (d.status) { r = \"yes\"; } } r;",
          "vehicle_info_status": "r = \"no\"; d = vehicle_info; if (Array.isArray(d)) { if (d.length && d[0].error) { r = \"error\"; } else if (d.length && d[0].status) { r = \"yes\"; } else if (d.length && d[0].requestId) { r = \"pending\"; } else if (d.length && d[0].statusDoc) { r = d[0].statusDoc; } } else if (d) { if (d.statusDoc) { r = d.statusDoc; } else if (d.requestId) { r = \"pending\"; } else if (d.error) { r = \"error\"; } else if (d.status) { r = \"yes\"; } } r;"
        }
      }
    ],
    "attrMapping": {
      "fn": "{{.person.firstName}}",
      "ln": "{{.person.lastName}}",
      "mn": "{{.person.middleName}}",
      "un": "",
      "e": "",
      "p": "",
      "l": "",
      "xid": "",
      "rid": "state",
      "x": {
        "rf_passport_status": "{{.rf_passport_status}}",
        "kid_rf_passport_status": "{{.kid_rf_passport_status}}",
        "frgn_pass_status": "{{.frgn_pass_status}}",
        "fid_doc_status": "{{.fid_doc_status}}",
        "passport_history_status": "{{.passport_history_status}}",
        "gibdd_driver_license_status": "{{.gibdd_driver_license_status}}",
        "vehicle_info_status": "{{.vehicle_info_status}}"
      }
    },
    "isActive": false
  }
]

Обсуждение