Перейти к основному контенту

Использование Эфириума для аутентификации в Веб2

Веб2
аутентификация
eas
Для начинающих
Ори Померанц
30 апреля 2025 г.
18 минут на чтение
Редактировать страницу (opens in a new tab)

Введение

SAML (opens in a new tab) — это стандарт, используемый в Веб2, который позволяет поставщику учетных данных (IdP) (opens in a new tab) предоставлять информацию о пользователе [поставщикам услуг (SP)](https://en.wikipedia.org/wiki/Service_provider_(SAML) (opens in a new tab).

В этом руководстве вы узнаете, как интегрировать подписи Эфириума с SAML, чтобы позволить пользователям использовать свои кошельки Эфириума для аутентификации в сервисах Веб2, которые еще не поддерживают Эфириум нативно.

Обратите внимание, что это руководство написано для двух разных аудиторий:

  • Специалистов по Эфириуму, которые понимают Эфириум и хотят изучить SAML
  • Специалистов по Веб2, которые понимают SAML и аутентификацию в Веб2 и хотят изучить Эфириум

В результате оно будет содержать много вводного материала, который вы уже знаете. Можете смело его пропускать.

SAML для специалистов по Эфириуму

SAML — это централизованный протокол. Поставщик услуг (SP) принимает утверждения (например, «это мой пользователь Джон, у него должны быть разрешения на выполнение A, B и C») от поставщика учетных данных (IdP) только в том случае, если у него есть заранее установленные доверительные отношения либо с ним, либо с центром сертификации (opens in a new tab), который подписал сертификат этого IdP.

Например, SP может быть туристическим агентством, предоставляющим туристические услуги компаниям, а IdP — внутренним веб-сайтом компании. Когда сотрудникам нужно забронировать деловую поездку, туристическое агентство отправляет их на аутентификацию в компанию, прежде чем позволить им фактически забронировать поездку.

Step by step SAML process

Именно так три сущности — браузер, SP и IdP — договариваются о доступе. SP не нужно заранее ничего знать о пользователе, использующем браузер, достаточно лишь доверять IdP.

Эфириум для специалистов по SAML

Эфириум — это децентрализованная система.

Ethereum logon

У пользователей есть приватный ключ (обычно хранящийся в расширении браузера). Из приватного ключа можно получить открытый ключ, а из него — 20-байтовый адрес. Когда пользователям нужно войти в систему, их просят подписать сообщение с нонсом (одноразовым значением). Сервер может проверить, что подпись была создана этим адресом.

Getting extra data from attestations

Подпись подтверждает только адрес Эфириума. Чтобы получить другие атрибуты пользователя, обычно используются аттестации (opens in a new tab). Аттестация обычно имеет следующие поля:

  • Attestor (Аттестующий) — адрес, который создал аттестацию
  • Recipient (Получатель) — адрес, к которому относится аттестация
  • Data (Данные) — аттестуемые данные, такие как имя, разрешения и т. д.
  • Schema (Схема) — ID схемы, используемой для интерпретации данных.

Из-за децентрализованной природы Эфириума любой пользователь может создавать аттестации. Личность аттестующего важна для определения того, какие аттестации мы считаем надежными.

Настройка

Первый шаг — настроить взаимодействие между SAML SP и SAML IdP.

  1. Загрузите программное обеспечение. Пример программного обеспечения для этой статьи находится на GitHub (opens in a new tab). Различные этапы хранятся в разных ветках, для этого этапа вам понадобится saml-only

    git clone https://github.com/qbzzt/250420-saml-ethereum -b saml-only
    cd 250420-saml-ethereum
    pnpm install
    
  2. Создайте ключи с самоподписанными сертификатами. Это означает, что ключ сам является своим центром сертификации и должен быть импортирован вручную поставщику услуг. Дополнительную информацию см. в документации OpenSSL (opens in a new tab).

    mkdir keys
    cd keys
    openssl req -new -x509 -days 365 -nodes -sha256 -out saml-sp.crt -keyout saml-sp.pem -subj /CN=sp/
    openssl req -new -x509 -days 365 -nodes -sha256 -out saml-idp.crt -keyout saml-idp.pem -subj /CN=idp/
    cd ..
    
  3. Запустите серверы (как SP, так и IdP)

    pnpm start
    
  4. Перейдите к SP по URL-адресу http://localhost:3000/ (opens in a new tab) и нажмите кнопку, чтобы вас перенаправило к IdP (порт 3001).

  5. Укажите IdP свой адрес электронной почты и нажмите Login to the service provider (Войти к поставщику услуг). Убедитесь, что вас перенаправило обратно к поставщику услуг (порт 3000) и что он узнает вас по вашему адресу электронной почты.

Подробное объяснение

Вот что происходит шаг за шагом:

Normal SAML logon without Ethereum

src/config.mts

Этот файл содержит конфигурацию как для поставщика учетных данных, так и для поставщика услуг. Обычно это две разные сущности, но здесь мы можем использовать общий код для простоты.

const fs = await import("fs")

const protocol="http"

Пока мы просто тестируем, поэтому использовать HTTP вполне нормально.

export const spCert = fs.readFileSync("keys/saml-sp.crt").toString()
export const idpCert = fs.readFileSync("keys/saml-idp.crt").toString()

Считайте открытые ключи, которые обычно доступны обоим компонентам (и либо доверяются напрямую, либо подписаны доверенным центром сертификации).

URL-адреса для обоих компонентов.

export const spPublicData = {

Публичные данные для поставщика услуг.

    entityID: `${spUrl}/metadata`,

По соглашению, в SAML entityID — это URL-адрес, по которому доступны метаданные сущности. Эти метаданные соответствуют публичным данным здесь, за исключением того, что они представлены в формате XML.

Самое важное определение для наших целей — это assertionConsumerServer. Это означает, что для передачи утверждения (например, «пользователь, который отправляет вам эту информацию, — somebody@example.com (opens email client)») поставщику услуг нам нужно использовать HTTP POST (opens in a new tab) на URL-адрес http://localhost:3000/sp/assertion.

Публичные данные для поставщика учетных данных аналогичны. В них указано, что для входа пользователя нужно отправить POST-запрос на http://localhost:3001/idp/login, а для выхода — POST-запрос на http://localhost:3001/idp/logout.

src/sp.mts

Это код, который реализует поставщика услуг.

import * as config from "./config.mts"
const fs = await import("fs")
const saml = await import("samlify")

Мы используем библиотеку samlify (opens in a new tab) для реализации SAML.

import * as validator from "@authenio/samlify-node-xmllint"
saml.setSchemaValidator(validator)

Библиотека samlify ожидает наличия пакета для проверки правильности XML, его подписи ожидаемым открытым ключом и т. д. Для этой цели мы используем @authenio/samlify-node-xmllint (opens in a new tab).

const express = (await import("express")).default
const spRouter = express.Router()
const app = express()

Router (opens in a new tab) в express (opens in a new tab) — это «мини-веб-сайт», который можно встроить внутрь веб-сайта. В данном случае мы используем его, чтобы сгруппировать все определения поставщика услуг вместе.

const spPrivateKey = fs.readFileSync("keys/saml-sp.pem").toString()

const sp = saml.ServiceProvider({
  privateKey: spPrivateKey,  
  ...config.spPublicData
})

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

const idp = saml.IdentityProvider(config.idpPublicData);

Публичные данные содержат все, что поставщику услуг нужно знать о поставщике учетных данных.

spRouter.get(`/metadata`, 
  (req, res) => res.header("Content-Type", "text/xml").send(sp.getMetadata())
)

Для обеспечения интероперабельности с другими компонентами SAML поставщики услуг и учетных данных должны предоставлять свои публичные данные (называемые метаданными) в формате XML по адресу /metadata.

spRouter.post(`/assertion`,

Это страница, к которой обращается браузер для идентификации. Утверждение включает идентификатор пользователя (здесь мы используем адрес электронной почты) и может включать дополнительные атрибуты. Это обработчик для шага 7 на диаграмме последовательности выше.

  async (req, res) => {
    // console.log(`SAML-ответ:\n${Buffer.from(req.body.SAMLResponse, 'base64').toString('utf-8')}`)

Вы можете использовать закомментированную команду, чтобы увидеть XML-данные, предоставленные в утверждении. Они закодированы в base64 (opens in a new tab).

    try {
      const loginResponse = await sp.parseLoginResponse(idp, 'post', req);

Разбор запроса на вход от сервера учетных данных.

      res.send(`
        <html>
          <body>
            <h2>Hello ${loginResponse.extract.nameID}</h2>
          </body>
        </html>
      `)
      res.send();

Отправка HTML-ответа, просто чтобы показать пользователю, что мы получили данные для входа.

    } catch (err) {
      console.error('Error processing SAML response:', err);
      res.status(400).send('SAML authentication failed');
    }
  }
)

Информирование пользователя в случае сбоя.

spRouter.get('/login',

Создание запроса на вход, когда браузер пытается получить эту страницу. Это обработчик для шага 1 на диаграмме последовательности выше.

  async (req, res) => {
    const loginRequest = await sp.createLoginRequest(idp, "post")

Получение информации для отправки POST-запроса на вход.

    res.send(`
      <html>
        <body>
          <script>
            window.onload = function () { document.forms[0].submit(); }            
          </script>

Эта страница отправляет форму (см. ниже) автоматически. Таким образом, пользователю не нужно ничего делать для перенаправления. Это шаг 2 на диаграмме последовательности выше.

          <form method="post" action="${loginRequest.entityEndpoint}">

POST-запрос на loginRequest.entityEndpoint (URL-адрес конечной точки поставщика учетных данных).

            <input type="hidden" name="${loginRequest.type}" value="${loginRequest.context}" />

Имя поля ввода — loginRequest.type (SAMLRequest). Содержимое этого поля — loginRequest.context, которое снова представляет собой XML, закодированный в base64.

          </form>
        </body>
      </html>
    `)    
  }
)

app.use(express.urlencoded({extended: true}))

Это промежуточное ПО (middleware) (opens in a new tab) считывает тело HTTP-запроса (opens in a new tab). По умолчанию express игнорирует его, так как для большинства запросов оно не требуется. Нам оно нужно, потому что POST использует тело запроса.

app.use(`/${config.spDir}`, spRouter)

Монтирование маршрутизатора в каталог поставщика услуг (/sp).

Если браузер пытается получить корневой каталог, предоставьте ему ссылку на страницу входа.

app.listen(config.spPort, () => {
  console.log(`service provider is running on http://${config.spHostname}:${config.spPort}`)
})

Прослушивание spPort с помощью этого приложения express.

src/idp.mts

Это поставщик учетных данных. Он очень похож на поставщика услуг, приведенные ниже объяснения касаются только тех частей, которые отличаются.

const xmlParser = new (await import("fast-xml-parser")).XMLParser(
  {
    ignoreAttributes: false, // Сохранить атрибуты
    attributeNamePrefix: "@_", // Префикс для атрибутов
  }
)

Нам нужно прочитать и понять XML-запрос, который мы получаем от поставщика услуг.

const getLoginPage = requestId => `

Эта функция создает страницу с автоматически отправляемой формой, которая возвращается на шаге 4 диаграммы последовательности выше.

Мы отправляем поставщику услуг два поля:

  1. requestId, на который мы отвечаем.
  2. Идентификатор пользователя (пока мы используем адрес электронной почты, предоставленный пользователем).
    </form>
  </body>
</html>

const idpRouter = express.Router()

idpRouter.post("/loginSubmitted", async (req, res) => {
  const loginResponse = await idp.createLoginResponse(

Это обработчик для шага 5 диаграммы последовательности выше. idp.createLoginResponse (opens in a new tab) создает ответ на вход.

    sp, 
    {
      authnContextClassRef: 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport',
      audience: sp.entityID,

Аудитория — это поставщик услуг.

      extract: {
        request: {
          id: req.body.requestId
        }
      },

Информация, извлеченная из запроса. Единственный параметр, который нас интересует в запросе, — это requestId, который позволяет поставщику услуг сопоставлять запросы и ответы на них.

      signingKey: { privateKey: idpPrivateKey, publicKey: config.idpCert }  // Обеспечить наличие подписи

Нам нужен signingKey, чтобы иметь данные для подписи ответа. Поставщик услуг не доверяет неподписанным запросам.

    },
    "post",
    {
      email: req.body.email

Это поле с информацией о пользователе, которую мы отправляем обратно поставщику услуг.

Снова используем автоматически отправляемую форму. Это шаг 6 диаграммы последовательности выше.


// Эндпоинт IdP для запросов на вход
idpRouter.post(`/login`,

Это конечная точка, которая получает запрос на вход от поставщика услуг. Это обработчик для шага 3 диаграммы последовательности выше.

  async (req, res) => {
    try {
      // Обходной путь, так как мне не удалось заставить parseLoginRequest работать.
      // const loginRequest = await idp.parseLoginRequest(sp, 'post', req)
      const samlRequest = xmlParser.parse(Buffer.from(req.body.SAMLRequest, 'base64').toString('utf-8'))
      res.send(getLoginPage(samlRequest["samlp:AuthnRequest"]["@_ID"]))

Мы должны иметь возможность использовать idp.parseLoginRequest (opens in a new tab) для чтения ID запроса на аутентификацию. Однако мне не удалось заставить это работать, и не стоило тратить на это много времени, поэтому я просто использую универсальный XML-парсер (opens in a new tab). Нужная нам информация — это атрибут ID внутри тега <samlp:AuthnRequest>, который находится на верхнем уровне XML.

Использование подписей Эфириума

Теперь, когда мы можем отправить идентификатор пользователя поставщику услуг, следующий шаг — получить идентификатор пользователя надежным способом. Viem позволяет нам просто запросить у кошелька адрес пользователя, но это означает запрос информации у браузера. Мы не контролируем браузер, поэтому не можем автоматически доверять полученному от него ответу.

Вместо этого IdP отправит браузеру строку для подписи. Если кошелек в браузере подпишет эту строку, это означает, что это действительно тот самый адрес (то есть он знает приватный ключ, соответствующий этому адресу).

Чтобы увидеть это в действии, остановите существующие IdP и SP и выполните следующие команды:

git checkout eth-signatures
pnpm install
pnpm start

Затем перейдите к SP (opens in a new tab) и следуйте инструкциям.

Обратите внимание, что на данном этапе мы не знаем, как получить адрес электронной почты из адреса Эфириума, поэтому вместо этого мы сообщаем SP <ethereum address>@bad.email.address.

Подробное объяснение

Изменения касаются шагов 4-5 на предыдущей диаграмме.

SAML with an Ethereum signature

Единственный файл, который мы изменили, — это idp.mts. Вот измененные части.

import { v4 as uuidv4 } from 'uuid'
import { verifyMessage } from 'viem'

Нам нужны эти две дополнительные библиотеки. Мы используем uuid (opens in a new tab) для создания значения нонса (opens in a new tab). Само значение не имеет значения, важен лишь тот факт, что оно используется только один раз.

Библиотека viem (opens in a new tab) позволяет нам использовать определения Эфириума. Здесь она нужна нам для проверки того, что подпись действительно действительна.

const loginPrompt = "To access the service provider, sign this nonce: "

Кошелек запрашивает у пользователя разрешение на подпись сообщения. Сообщение, состоящее только из нонса, может сбить пользователей с толку, поэтому мы включаем эту подсказку.

// Сохранять requestIDs здесь
let nonces = {}

Нам нужна информация о запросе, чтобы иметь возможность ответить на него. Мы могли бы отправить ее вместе с запросом (шаг 4) и получить обратно (шаг 5). Однако мы не можем доверять информации, которую получаем от браузера, находящегося под контролем потенциально враждебного пользователя. Поэтому лучше хранить ее здесь, используя нонс в качестве ключа.

Обратите внимание, что ради простоты мы делаем это здесь в виде переменной. Однако у этого есть несколько недостатков:

  • Мы уязвимы для атаки типа «отказ в обслуживании» (DoS). Злоумышленник может попытаться войти в систему несколько раз, переполняя нашу память.
  • Если процесс IdP потребуется перезапустить, мы потеряем существующие значения.
  • Мы не можем балансировать нагрузку между несколькими процессами, потому что у каждого будет своя переменная.

В рабочей системе мы бы использовали базу данных и реализовали какой-либо механизм истечения срока действия.

const getSignaturePage = requestId => {
  const nonce = uuidv4()
  nonces[nonce] = requestId

Создание нонса и сохранение requestId для будущего использования.

  return `
<html>
  <head>
    <script type="module">

Этот JavaScript выполняется автоматически при загрузке страницы.

      import { createWalletClient, custom, getAddress } from 'https://esm.sh/viem'

Нам нужно несколько функций из viem.

      if (!window.ethereum) {
          alert("Please install MetaMask or a compatible wallet and then reload")
      }

Мы можем работать только в том случае, если в браузере есть кошелек.

      const [account] = await window.ethereum.request({method: 'eth_requestAccounts'})

Запрос списка аккаунтов у кошелька (window.ethereum). Предполагаем, что есть хотя бы один, и сохраняем только первый.

      const walletClient = createWalletClient({
          account,
          transport: custom(window.ethereum)
      })

Создание клиента кошелька (opens in a new tab) для взаимодействия с кошельком браузера.

      window.goodSignature = () => {
        walletClient.signMessage({
            message: "${loginPrompt}${nonce}"

Просим пользователя подписать сообщение. Поскольку весь этот HTML находится в шаблонной строке (opens in a new tab), мы можем использовать переменные, определенные в процессе idp. Это шаг 4.5 на диаграмме последовательности.

        }).then(signature => {
            const path= "/${config.idpDir}/signature/${nonce}/" + account + "/" + signature
            window.location.href = path
        })
      }

Перенаправление на /idp/signature/<nonce>/<address>/<signature>. Это шаг 5 на диаграмме последовательности.

      window.badSignature = () => {
        const path= "/${config.idpDir}/signature/${nonce}/" + 
          getAddress("0x" + "BAD060A7".padEnd(40, "0")) + 
          "/0x" + "BAD0516".padStart(130, "0")
        window.location.href = path
      }

Подпись отправляется обратно браузером, который потенциально может быть вредоносным (ничто не мешает вам просто открыть http://localhost:3001/idp/signature/bad-nonce/bad-address/bad-signature в браузере). Поэтому важно убедиться, что процесс IdP правильно обрабатывает недействительные подписи.

Остальное — просто стандартный HTML.

idpRouter.get("/signature/:nonce/:account/:signature", async (req, res) => {

Это обработчик для шага 5 на диаграмме последовательности.

  const requestId = nonces[req.params.nonce]
  if (requestId === undefined) {
    res.send("Bad nonce")
    return ;
  }  
  
  nonces[req.params.nonce] = undefined

Получение ID запроса и удаление нонса из nonces, чтобы убедиться, что его нельзя использовать повторно.

  try {

Поскольку существует множество причин, по которым подпись может быть недействительной, мы оборачиваем это в блок try ... catch, чтобы перехватить любые выброшенные ошибки.

    const validSignature = await verifyMessage({
      address: req.params.account,
      message: `${loginPrompt}${req.params.nonce}`,
      signature: req.params.signature
    })

Использование verifyMessage (opens in a new tab) для реализации шага 5.5 на диаграмме последовательности.

    if (!validSignature)
      throw("Bad signature")
  } catch (err) {
    res.send("Error:" + err)
    return ;
  }

Остальная часть обработчика эквивалентна тому, что мы делали в обработчике /loginSubmitted ранее, за исключением одного небольшого изменения.

  const loginResponse = await idp.createLoginResponse(
      .
      .
      .
    {
      email: req.params.account + "@bad.email.address"
    }
  );

У нас нет фактического адреса электронной почты (мы получим его в следующем разделе), поэтому пока мы возвращаем адрес Эфириума и четко помечаем, что это не адрес электронной почты.

Вместо getLoginPage теперь используйте getSignaturePage в обработчике шага 3.

Получение адреса электронной почты

Следующий шаг — получить адрес электронной почты, идентификатор, запрашиваемый поставщиком услуг. Для этого мы используем Ethereum Attestation Service (EAS) (opens in a new tab).

Самый простой способ получить аттестации — использовать GraphQL API (opens in a new tab). Мы используем этот запрос:

Эта schemaId (opens in a new tab) включает только адрес электронной почты. Этот запрос запрашивает аттестации по этой схеме. Субъект аттестации называется recipient. Это всегда адрес Эфириума.

Предупреждение: Способ, которым мы получаем аттестации здесь, имеет две проблемы с безопасностью.

  • Мы обращаемся к конечной точке API https://optimism.easscan.org/graphql, которая является централизованным компонентом. Мы можем получить атрибут id, а затем выполнить поиск ончейн, чтобы убедиться, что аттестация настоящая, но конечная точка API все равно может цензурировать аттестации, не сообщая нам о них.

    Эту проблему можно решить: мы могли бы запустить собственную конечную точку GraphQL и получать аттестации из логов цепи, но для наших целей это излишне.

  • Мы не проверяем личность аттестующего. Кто угодно может предоставить нам ложную информацию. В реальной реализации у нас был бы набор доверенных аттестующих, и мы бы рассматривали только их аттестации.

Чтобы увидеть это в действии, остановите существующие IdP и SP и выполните следующие команды:

git checkout email-address
pnpm install
pnpm start

Затем укажите свой адрес электронной почты. У вас есть два способа сделать это:

  • Импортировать кошелек с помощью приватного ключа и использовать тестовый приватный ключ 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80.

  • Добавить аттестацию для собственного адреса электронной почты:

    1. Перейдите к схеме в обозревателе аттестаций (opens in a new tab).

    2. Нажмите Attest with Schema (Аттестовать с помощью схемы).

    3. Введите свой адрес Эфириума в качестве получателя, свой адрес электронной почты в поле email address и выберите Onchain. Затем нажмите Make Attestation (Создать аттестацию).

    4. Одобрите транзакцию в своем кошельке. Вам понадобится немного ETH в блокчейне Optimism (opens in a new tab) для оплаты газа.

В любом случае, после того как вы это сделаете, перейдите по адресу http://localhost:3000 (opens in a new tab) и следуйте инструкциям. Если вы импортировали тестовый приватный ключ, вы получите адрес электронной почты test_addr_0@example.com. Если вы использовали свой собственный адрес, это будет тот адрес, который вы аттестовали.

Подробное объяснение

Getting from Ethereum address to e-mail

Новые шаги — это взаимодействие с GraphQL, шаги 5.6 и 5.7.

Опять же, вот измененные части idp.mts.

import { GraphQLClient } from 'graphql-request'
import { SchemaEncoder } from '@ethereum-attestation-service/eas-sdk'

Импорт необходимых нам библиотек.

const graphqlEndpointUrl = "https://optimism.easscan.org/graphql"

Существует отдельная конечная точка для каждого блокчейна (opens in a new tab).

const graphqlClient = new GraphQLClient(graphqlEndpointUrl, { fetch })

Создание нового клиента GraphQLClient, который мы можем использовать для запросов к конечной точке.

const graphqlSchema = 'string emailAddress'
const graphqlEncoder = new SchemaEncoder(graphqlSchema)

GraphQL дает нам только непрозрачный объект данных с байтами. Чтобы понять его, нам нужна схема.

const ethereumAddressToEmail = async ethAddr => {

Функция для получения адреса электронной почты из адреса Эфириума.

  const query = `
    query GetAttestationsByRecipient {

Это запрос GraphQL.

      attestations(

Мы ищем аттестации.

        where: { 
          recipient: { equals: "${getAddress(ethAddr)}" }
          schemaId: { equals: "0xfa2eff59a916e3cc3246f9aec5e0ca00874ae9d09e4678e5016006f07622f977" }
        }

Нам нужны аттестации в нашей схеме, где получателем является getAddress(ethAddr). Функция getAddress (opens in a new tab) гарантирует, что наш адрес имеет правильную контрольную сумму (opens in a new tab). Это необходимо, так как GraphQL чувствителен к регистру. «0xBAD060A7», «0xBad060A7» и «0xbad060a7» — это разные значения.

        take: 1

Независимо от того, сколько аттестаций мы найдем, нам нужна только первая.

      ) {
        data
        id
        attester
      }
    }`

Поля, которые мы хотим получить.

  • attester: Адрес, который отправил аттестацию. Обычно это используется для принятия решения о том, доверять аттестации или нет.
  • id: ID аттестации. Вы можете использовать это значение, чтобы прочитать аттестацию ончейн (opens in a new tab) и убедиться, что информация из запроса GraphQL верна.
  • data: Данные схемы (в данном случае адрес электронной почты).
  const queryResult = await graphqlClient.request(query)

  if (queryResult.attestations.length == 0)
    return "no_address@available.is"

Если аттестации нет, возвращаем значение, которое явно неверно, но будет казаться действительным для поставщика услуг.

  const attestationDataFields = graphqlEncoder.decodeData(queryResult.attestations[0].data)
  return attestationDataFields[0].value.value
}

Если значение есть, используем decodeData для декодирования данных. Нам не нужны предоставляемые им метаданные, только само значение.

Использование новой функции для получения адреса электронной почты.

Как насчет децентрализации?

В этой конфигурации пользователи не могут выдавать себя за тех, кем они не являются, пока мы полагаемся на надежных аттестующих для сопоставления адреса Эфириума с адресом электронной почты. Однако наш поставщик учетных данных по-прежнему является централизованным компонентом. Тот, у кого есть приватный ключ поставщика учетных данных, может отправить ложную информацию поставщику услуг.

Возможно, существует решение с использованием многосторонних вычислений (MPC) (opens in a new tab). Я надеюсь написать об этом в будущем руководстве.

Заключение

Внедрение стандарта входа, такого как подписи Эфириума, сталкивается с проблемой курицы и яйца. Поставщики услуг хотят привлечь как можно более широкий рынок. Пользователи хотят иметь возможность получать доступ к сервисам, не беспокоясь о поддержке своего стандарта входа. Создание адаптеров, таких как Ethereum IdP, может помочь нам преодолеть это препятствие.

Здесь вы можете найти больше моих работ (opens in a new tab).

Последнее обновление страницы: 3 апреля 2026 г.