Ruka kwenda kwenye maudhui makuu

Kutumia Ethereum kwa uthibitishaji wa web2

web2
uthibitishaji
eas
Mwanzo
Ori Pomerantz
30 Aprili 2025
19 soma ndani ya dakika

Utangulizi

SAML (opens in a new tab) ni kiwango kinachotumika kwenye web2 kuruhusu mtoa huduma wa kitambulisho (IdP) (opens in a new tab) kutoa taarifa za mtumiaji kwa watoa huduma (SP) (opens in a new tab).

Katika mafunzo haya unajifunza jinsi ya kuunganisha saini za Ethereum na SAML ili kuwaruhusu watumiaji kutumia mikoba yao ya Ethereum kujithibitisha kwa huduma za web2 ambazo bado hazitumii Ethereum moja kwa moja.

Kumbuka kwamba mafunzo haya yameandikwa kwa ajili ya hadhira mbili tofauti:

  • Watu wa Ethereum wanaoielewa Ethereum na wanahitaji kujifunza SAML
  • Watu wa Web2 wanaoielewa SAML na uthibitishaji wa web2 na wanahitaji kujifunza Ethereum

Kutokana na hilo, itaendana na kuwa na nyenzo nyingi za utangulizi ambazo tayari unazijua. Jisikie huru kuiruka.

SAML kwa watu wa Ethereum

SAML ni itifaki ya kati. Mtoa huduma (SP) anakubali tu madai (kama vile "huyu ni mtumiaji wangu John, anapaswa kuwa na ruhusa za kufanya A, B, na C") kutoka kwa mtoa huduma wa kitambulisho (IdP) ikiwa ina uhusiano wa awali wa kuaminiana nayo, au na mamlaka ya cheti (opens in a new tab) iliyosaini cheti cha IdP hicho.

Kwa mfano, SP inaweza kuwa wakala wa usafiri unaotoa huduma za usafiri kwa makampuni, na IdP inaweza kuwa tovuti ya ndani ya kampuni. Wakati wafanyakazi wanapohitaji kuweka nafasi ya safari za kibiashara, wakala wa usafiri huwatuma kwa ajili ya uthibitishaji na kampuni kabla ya kuwaruhusu kuweka nafasi ya safari.

Mchakato wa SAML hatua kwa hatua

Hii ndiyo njia ambayo taasisi tatu, kivinjari, SP, na IdP, hujadiliana kwa ajili ya ufikiaji. SP haihitaji kujua chochote kuhusu mtumiaji anayetumia kivinjari mapema, ila tu kumwamini IdP.

Ethereum kwa watu wa SAML

Ethereum ni mfumo uliotawanywa.

Kuingia kwa Ethereum

Watumiaji wana ufunguo binafsi (kwa kawaida huhifadhiwa kwenye kiendelezi cha kivinjari). Kutoka kwenye ufunguo binafsi unaweza kupata ufunguo wa umma, na kutoka hapo anwani ya baiti 20. Wakati watumiaji wanapohitaji kuingia kwenye mfumo, wanaombwa kusaini ujumbe wenye nonce (thamani ya matumizi moja). Seva inaweza kuthibitisha saini iliundwa na anwani hiyo.

Kupata data ya ziada kutoka kwa uthibitisho

Saini inathibitisha tu anwani ya Ethereum. Ili kupata sifa zingine za mtumiaji, kwa kawaida unatumia uthibitisho (opens in a new tab). Uthibitisho kwa kawaida huwa na nyanja hizi:

  • Mthibitishaji, anwani iliyofanya uthibitisho
  • Mpokeaji, anwani ambayo uthibitisho unatumika kwake
  • Data, data inayothibitishwa, kama vile jina, ruhusa, n.k.
  • Schema, Kitambulisho cha schema inayotumika kutafsiri data.

Kwa sababu ya asili ya mfumo uliotawanywa wa Ethereum, mtumiaji yeyote anaweza kufanya uthibitisho. Utambulisho wa mthibitishaji ni muhimu kutambua ni uthibitisho upi tunaouzingatia kuwa wa kuaminika.

Mpangilio

Hatua ya kwanza ni kuwa na SAML SP na SAML IdP zinazowasiliana kati yao.

  1. Pakua programu. Programu ya mfano kwa makala hii iko kwenye github (opens in a new tab). Hatua tofauti huhifadhiwa katika matawi tofauti, kwa hatua hii unataka saml-only

    1git clone https://github.com/qbzzt/250420-saml-ethereum -b saml-only
    2cd 250420-saml-ethereum
    3pnpm install
  2. Unda funguo zenye vyeti vilivyosainiwa kibinafsi. Hii inamaanisha kuwa ufunguo ni mamlaka yake ya cheti, na inahitaji kuingizwa kwa mikono kwa mtoa huduma. Tazama hati za OpenSSL (opens in a new tab) kwa habari zaidi.

    1mkdir keys
    2cd keys
    3openssl req -new -x509 -days 365 -nodes -sha256 -out saml-sp.crt -keyout saml-sp.pem -subj /CN=sp/
    4openssl req -new -x509 -days 365 -nodes -sha256 -out saml-idp.crt -keyout saml-idp.pem -subj /CN=idp/
    5cd ..
  3. Anzisha seva (zote SP na IdP)

    1pnpm start
  4. Vinjari hadi kwa SP kwenye URL http://localhost:3000/ (opens in a new tab) na ubofye kitufe ili kuelekezwa kwa IdP (mlango 3001).

  5. Mpe IdP anwani yako ya barua pepe na ubofye Ingia kwa mtoa huduma. Tazama kuwa unaelekezwa tena kwa mtoa huduma (mlango 3000) na kwamba anakutambua kwa anwani yako ya barua pepe.

Maelezo ya kina

Hivi ndivyo inavyotokea, hatua kwa hatua:

Kuingia kwa kawaida kwa SAML bila Ethereum

src/config.mts

Faili hii ina usanidi kwa Mtoa Huduma wa Kitambulisho na Mtoa Huduma. Kwa kawaida hizi mbili zingekuwa taasisi tofauti, lakini hapa tunaweza kushiriki msimbo kwa urahisi.

1const fs = await import("fs")
2
3const protocol="http"

Kwa sasa tunafanya majaribio tu, kwa hivyo ni sawa kutumia HTTP.

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

Soma funguo za umma, ambazo kwa kawaida zinapatikana kwa vipengele vyote viwili (na aidha zinaaminika moja kwa moja, au zimesainiwa na mamlaka ya cheti inayoaminika).

1export const spPort = 3000
2export const spHostname = "localhost"
3export const spDir = "sp"
4
5export const idpPort = 3001
6export const idpHostname = "localhost"
7export const idpDir = "idp"
8
9export const spUrl = `${protocol}://${spHostname}:${spPort}/${spDir}`
10export const idpUrl = `${protocol}://${idpHostname}:${idpPort}/${idpDir}`
Onyesha yote

URL za vipengele vyote viwili.

1export const spPublicData = {

Data ya umma kwa mtoa huduma.

1 entityID: `${spUrl}/metadata`,

Kwa kawaida, katika SAML entityID ni URL ambapo metadata ya taasisi inapatikana. Metadata hii inalingana na data ya umma hapa, isipokuwa iko katika fomu ya XML.

1 wantAssertionsSigned: true,
2 authnRequestsSigned: false,
3 signingCert: spCert,
4 allowCreate: true,
5 assertionConsumerService: [{
6 Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
7 Location: `${spUrl}/assertion`,
8 }]
9 }
Onyesha yote

Ufafanuzi muhimu zaidi kwa madhumuni yetu ni assertionConsumerServer. Inamaanisha kuwa ili kudai kitu (kwa mfano, "mtumiaji anayekutumia habari hii ni somebody@example.com (opens email client)") kwa mtoa huduma tunahitaji kutumia HTTP POST (opens in a new tab) kwa URL http://localhost:3000/sp/assertion.

1export const idpPublicData = {
2 entityID: `${idpUrl}/metadata`,
3 signingCert: idpCert,
4 wantAuthnRequestsSigned: false,
5 singleSignOnService: [{
6 Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
7 Location: `${idpUrl}/login`
8 }],
9 singleLogoutService: [{
10 Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
11 Location: `${idpUrl}/logout`
12 }],
13 }
Onyesha yote

Data ya umma kwa mtoa huduma wa kitambulisho ni sawa. Inabainisha kuwa ili kumwingiza mtumiaji unatumia POST kwa http://localhost:3001/idp/login na kumtoa mtumiaji unatumia POST kwa http://localhost:3001/idp/logout.

src/sp.mts

Huu ndio msimbo unaotekeleza mtoa huduma.

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

Tunatumia maktaba ya samlify (opens in a new tab) kutekeleza SAML.

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

Maktaba ya samlify inatarajia kuwa na kifurushi kinachothibitisha kuwa XML ni sahihi, imesainiwa na ufunguo wa umma unaotarajiwa, n.k. Tunatumia @authenio/samlify-node-xmllint (opens in a new tab) kwa madhumuni haya.

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

Router (opens in a new tab) ya express (opens in a new tab) ni "tovuti ndogo" ambayo inaweza kuwekwa ndani ya tovuti. Katika kesi hii, tunaitumia kupanga ufafanuzi wote wa watoa huduma pamoja.

1const spPrivateKey = fs.readFileSync("keys/saml-sp.pem").toString()
2
3const sp = saml.ServiceProvider({
4 privateKey: spPrivateKey,
5 ...config.spPublicData
6})

Uwakilishi wa mtoa huduma wenyewe ni data yote ya umma, na ufunguo binafsi unaotumia kusaini habari.

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

Data ya umma ina kila kitu ambacho mtoa huduma anahitaji kujua kuhusu mtoa huduma wa kitambulisho.

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

Ili kuwezesha ushirikiano na vipengele vingine vya SAML, watoa huduma na watoa huduma za utambulisho wanapaswa kuwa na data zao za umma (zinazoitwa metadata) zinazopatikana katika muundo wa XML katika /metadata.

1spRouter.post(`/assertion`,

Hii ndiyo ukurasa unaofikiwa na kivinjari ili kujitambulisha. Madai hayo yanajumuisha kitambulisho cha mtumiaji (hapa tunatumia anwani ya barua pepe), na yanaweza kujumuisha sifa za ziada. Hiki ni kishikizi cha hatua ya 7 katika mchoro wa mfuatano hapo juu.

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

Unaweza kutumia amri iliyotolewa maoni ili kuona data ya XML iliyotolewa katika madai. Imesimbwa kwa base64 (opens in a new tab).

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

Changanua ombi la kuingia kutoka kwa seva ya utambulisho.

1 res.send(`
2 <html>
3 <body>
4 <h2>Hello ${loginResponse.extract.nameID}</h2>
5 </body>
6 </html>
7 `)
8 res.send();

Tuma jibu la HTML, ili tu kumwonyesha mtumiaji tulipata kuingia.

1 } catch (err) {
2 console.error('Error processing SAML response:', err);
3 res.status(400).send('SAML authentication failed');
4 }
5 }
6)

Mjulishe mtumiaji endapo kutakuwa na hitilafu.

1spRouter.get('/login',

Unda ombi la kuingia wakati kivinjari kinapojaribu kupata ukurasa huu. Hiki ni kishikizi cha hatua ya 1 katika mchoro wa mfuatano hapo juu.

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

Pata taarifa ya kutuma ombi la kuingia.

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

Ukurasa huu unawasilisha fomu (tazama hapa chini) kiotomatiki. Kwa njia hii mtumiaji hahitaji kufanya chochote ili aelekezwe. Hii ni hatua ya 2 katika mchoro wa mfuatano hapo juu.

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

Tuma kwa loginRequest.entityEndpoint (URL ya mwisho wa mtoa huduma wa utambulisho).

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

Jina la ingizo ni loginRequest.type (SAMLRequest). Maudhui ya uwanja huo ni loginRequest.context, ambayo tena ni XML iliyosimbwa kwa base64.

1 </form>
2 </body>
3 </html>
4 `)
5 }
6)
7
8app.use(express.urlencoded({extended: true}))

Middleware hii (opens in a new tab) inasoma mwili wa ombi la HTTP (opens in a new tab). Kwa chaguo-msingi, express inapuuzia, kwa sababu maombi mengi hayaihitaji. Tunaihitaji kwa sababu POST inatumia mwili.

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

Pachika kipanga njia katika saraka ya mtoa huduma (/sp).

1app.get("/", (req, res) => {
2 res.send(`
3 <html>
4 <body>
5 <button onClick="document.location.href='${config.spUrl}/login'">
6 Click here to log on
7 </button>
8 </body>
9 </html>
10 `)
11})
Onyesha yote

Ikiwa kivinjari kitajaribu kupata saraka ya mizizi, kipe kiungo cha ukurasa wa kuingia.

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

Sikiliza spPort na programu hii ya express.

src/idp.mts

Huyu ndiye mtoa huduma wa kitambulisho. Ni sawa na mtoa huduma, maelezo yaliyo hapa chini ni ya sehemu ambazo ni tofauti.

1const xmlParser = new (await import("fast-xml-parser")).XMLParser(
2 {
3 ignoreAttributes: false, // Preserve attributes
4 attributeNamePrefix: "@_", // Prefix for attributes
5 }
6)

Tunahitaji kusoma na kuelewa ombi la XML tunalopokea kutoka kwa mtoa huduma.

1const getLoginPage = requestId => `

Kazi hii huunda ukurasa na fomu inayojituma yenyewe ambayo inarudishwa katika hatua ya 4 ya mchoro wa mfuatano hapo juu.

1<html>
2 <head>
3 <title>Login page</title>
4 </head>
5 <body>
6 <h2>Login page</h2>
7 <form method="post" action="./loginSubmitted">
8 <input type="hidden" name="requestId" value="${requestId}" />
9 Email address: <input name="email" />
10 <br />
11 <button type="Submit">
12 Login to the service provider
13 </button>
Onyesha yote

Kuna nyanja mbili tunazotuma kwa mtoa huduma:

  1. requestId ambayo tunajibu.
  2. Kitambulisho cha mtumiaji (tunatumia anwani ya barua pepe ambayo mtumiaji hutoa kwa sasa).
1 </form>
2 </body>
3</html>
4
5const idpRouter = express.Router()
6
7idpRouter.post("/loginSubmitted", async (req, res) => {
8 const loginResponse = await idp.createLoginResponse(

Hiki ni kishikizi cha hatua ya 5 katika mchoro wa mfuatano hapo juu. idp.createLoginResponse (opens in a new tab) huunda jibu la kuingia.

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

Watazamaji ni watoa huduma.

1 extract: {
2 request: {
3 id: req.body.requestId
4 }
5 },

Taarifa iliyotolewa kutoka kwa ombi. Kigezo kimoja tunachojali katika ombi ni requestId, ambayo inaruhusu mtoa huduma kulinganisha maombi na majibu yao.

1 signingKey: { privateKey: idpPrivateKey, publicKey: config.idpCert } // Ensure signing

Tunahitaji signingKey ili kuwa na data ya kusaini jibu. Mtoa huduma haamini maombi ambayo hayajasainiwa.

1 },
2 "post",
3 {
4 email: req.body.email

Hili ni eneo lenye maelezo ya mtumiaji tunayorudisha kwa mtoa huduma.

1 }
2 );
3
4 res.send(`
5 <html>
6 <body>
7 <script>
8 window.onload = function () { document.forms[0].submit(); }
9 </script>
10
11 <form method="post" action="${loginResponse.entityEndpoint}">
12 <input type="hidden" name="${loginResponse.type}" value="${loginResponse.context}" />
13 </form>
14 </body>
15 </html>
16 `)
17})
Onyesha yote

Tena, tumia fomu inayojituma yenyewe. Hii ni hatua ya 6 ya mchoro wa mfuatano hapo juu.

1
2// IdP endpoint for login requests
3idpRouter.post(`/login`,

Hii ni sehemu ya mwisho inayopokea ombi la kuingia kutoka kwa mtoa huduma. Hii ni handler hatua ya 3 ya mchoro wa mfuatano hapo juu.

1 async (req, res) => {
2 try {
3 // Workaround because I couldn't get parseLoginRequest to work.
4 // const loginRequest = await idp.parseLoginRequest(sp, 'post', req)
5 const samlRequest = xmlParser.parse(Buffer.from(req.body.SAMLRequest, 'base64').toString('utf-8'))
6 res.send(getLoginPage(samlRequest["samlp:AuthnRequest"]["@_ID"]))

Tunapaswa kuwa na uwezo wa kutumia idp.parseLoginRequest (opens in a new tab) kusoma kitambulisho cha ombi la uthibitishaji. Hata hivyo, sikuweza kuifanyia kazi na haikustahili kutumia muda mwingi juu yake kwa hivyo ninatumia tu kichanganuzi cha XML cha jumla (opens in a new tab). Taarifa tunayohitaji ni sifa ya ID ndani ya lebo ya <samlp:AuthnRequest>, ambayo iko katika kiwango cha juu cha XML.

Kutumia saini za Ethereum

Sasa kwa kuwa tunaweza kutuma utambulisho wa mtumiaji kwa mtoa huduma, hatua inayofuata ni kupata utambulisho wa mtumiaji kwa njia inayoaminika. Viem inaturuhusu kuuliza tu mkoba kwa anwani ya mtumiaji, lakini hii inamaanisha kuuliza kivinjari kwa habari. Hatuidhibiti kivinjari, kwa hivyo hatuwezi kuamini moja kwa moja jibu tunalopata kutoka kwake.

Badala yake, IdP itatuma kivinjari mfuatano wa kusaini. Ikiwa mkoba katika kivinjari unasaini mfuatano huu, inamaanisha kuwa kweli ni anwani hiyo (yaani, inajua ufunguo binafsi unaolingana na anwani).

Ili kuona hili likitendeka, simamisha IdP na SP zilizopo na endesha amri hizi:

1git checkout eth-signatures
2pnpm install
3pnpm start

Kisha vinjari kwa SP (opens in a new tab) na ufuate maelekezo.

Kumbuka kuwa kwa wakati huu hatujui jinsi ya kupata anwani ya barua pepe kutoka kwa anwani ya Ethereum, kwa hivyo badala yake tunaripoti <ethereum address>@bad.email.address kwa SP.

Maelezo ya kina

Mabadiliko yapo katika hatua 4-5 katika mchoro uliopita.

SAML na saini ya Ethereum

Faili pekee tuliyobadilisha ni idp.mts. Hapa kuna sehemu zilizobadilishwa.

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

Tunahitaji maktaba hizi mbili za ziada. Tunatumia uuid (opens in a new tab) kuunda thamani ya nonce (opens in a new tab). Thamani yenyewe haijalishi, ni ukweli tu kwamba inatumika mara moja tu.

Maktaba ya viem (opens in a new tab) inaturuhusu kutumia ufafanuzi wa Ethereum. Hapa tunaihitaji ili kuthibitisha kuwa saini ni halali.

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

Mkoba unamwomba mtumiaji ruhusa ya kusaini ujumbe. Ujumbe ambao ni nonce tu unaweza kuwachanganya watumiaji, kwa hivyo tunajumuisha kidokezo hiki.

1// Keep requestIDs here
2let nonces = {}

Tunahitaji taarifa ya ombi ili tuweze kulijibu. Tunaweza kuituma na ombi (hatua ya 4), na kuipokea tena (hatua ya 5). Hata hivyo, hatuwezi kuamini taarifa tunazopata kutoka kwenye kivinjari, ambacho kiko chini ya udhibiti wa mtumiaji anayeweza kuwa na nia mbaya. Kwa hivyo ni bora kuihifadhi hapa, na nonce kama ufunguo.

Kumbuka kuwa tunafanya hivi hapa kama kigezo kwa ajili ya urahisi. Hata hivyo, hii ina hasara kadhaa:

  • Tuko katika hatari ya shambulio la kunyimwa huduma. Mtumiaji hasidi anaweza kujaribu kuingia mara nyingi, akijaza kumbukumbu zetu.
  • Ikiwa mchakato wa IdP unahitaji kuanzishwa upya, tunapoteza maadili yaliyopo.
  • Hatuwezi kusawazisha mzigo katika michakato mingi, kwa sababu kila moja ingekuwa na kigezo chake.

Katika mfumo wa uzalishaji tungetumia hifadhidata na kutekeleza aina fulani ya utaratibu wa kumalizika muda.

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

Unda nonce, na uhifadhi requestId kwa matumizi ya baadaye.

1 return `
2<html>
3 <head>
4 <script type="module">

JavaScript hii inatekelezwa kiotomatiki wakati ukurasa unapopakiwa.

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

Tunahitaji kazi kadhaa kutoka kwa viem.

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

Tunaweza kufanya kazi tu ikiwa kuna mkoba kwenye kivinjari.

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

Omba orodha ya akaunti kutoka kwa mkoba (window.ethereum). Fikiria kuna angalau moja, na hifadhi tu ya kwanza.

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

Unda mteja wa mkoba (opens in a new tab) ili kuingiliana na mkoba wa kivinjari.

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

Mwombe mtumiaji kusaini ujumbe. Kwa sababu HTML hii yote iko katika kamba ya kiolezo (opens in a new tab), tunaweza kutumia vigezo vilivyofafanuliwa katika mchakato wa idp. Hii ni hatua ya 4.5 katika mchoro wa mfuatano.

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

Elekeza kwa /idp/signature/<nonce>/<address>/<signature>. Hii ni hatua ya 5 katika mchoro wa mfuatano.

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

Saini inarudishwa na kivinjari, ambacho kinaweza kuwa hasidi (hakuna kitu cha kukuzuia kufungua http://localhost:3001/idp/signature/bad-nonce/bad-address/bad-signature kwenye kivinjari). Kwa hivyo, ni muhimu kuthibitisha kuwa mchakato wa IdP unashughulikia saini mbaya ipasavyo.

1 </script>
2 </head>
3 <body>
4 <h2>Please sign</h2>
5 <button onClick="window.goodSignature()">
6 Submit a good (valid) signature
7 </button>
8 <br/>
9 <button onClick="window.badSignature()">
10 Submit a bad (invalid) signature
11 </button>
12 </body>
13</html>
14`
15}
Onyesha yote

Sehemu iliyobaki ni HTML ya kawaida tu.

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

Hiki ni kishikizi cha hatua ya 5 katika mchoro wa mfuatano.

1 const requestId = nonces[req.params.nonce]
2 if (requestId === undefined) {
3 res.send("Bad nonce")
4 return ;
5 }
6
7 nonces[req.params.nonce] = undefined

Pata kitambulisho cha ombi, na ufute nonce kutoka kwa nonces ili kuhakikisha haiwezi kutumika tena.

1 try {

Kwa sababu kuna njia nyingi ambazo saini inaweza kuwa batili, tunafunga hii katika try ... kuzuia catch ili kunasa makosa yoyote yaliyorushwa.

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

Tumia verifyMessage (opens in a new tab) kutekeleza hatua ya 5.5 katika mchoro wa mfuatano.

1 if (!validSignature)
2 throw("Bad signature")
3 } catch (err) {
4 res.send("Error:" + err)
5 return ;
6 }

Sehemu iliyobaki ya mshikaji ni sawa na kile tumefanya katika mshikaji wa /loginSubmitted hapo awali, isipokuwa kwa mabadiliko madogo.

1 const loginResponse = await idp.createLoginResponse(
2 .
3 .
4 .
5 {
6 email: req.params.account + "@bad.email.address"
7 }
8 );

Hatuna anwani halisi ya barua pepe (tutaipata katika sehemu inayofuata), kwa hivyo kwa sasa tunarudisha anwani ya Ethereum na kuiweka alama wazi kuwa si anwani ya barua pepe.

1// IdP endpoint for login requests
2idpRouter.post(`/login`,
3 async (req, res) => {
4 try {
5 // Workaround because I couldn't get parseLoginRequest to work.
6 // const loginRequest = await idp.parseLoginRequest(sp, 'post', req)
7 const samlRequest = xmlParser.parse(Buffer.from(req.body.SAMLRequest, 'base64').toString('utf-8'))
8 res.send(getSignaturePage(samlRequest["samlp:AuthnRequest"]["@_ID"]))
9 } catch (err) {
10 console.error('Error processing SAML response:', err);
11 res.status(400).send('SAML authentication failed');
12 }
13 }
14)
Onyesha yote

Badala ya getLoginPage, sasa tumia getSignaturePage katika kishikilizi cha hatua ya 3.

Kupata anwani ya barua pepe

Hatua inayofuata ni kupata anwani ya barua pepe, kitambulisho kinachoombwa na mtoa huduma. Ili kufanya hivyo, tunatumia Huduma ya Uthibitisho ya Ethereum (EAS) (opens in a new tab).

Njia rahisi zaidi ya kupata uthibitisho ni kutumia GraphQL API (opens in a new tab). Tunatumia swali hili:

1query GetAttestationsByRecipient {
2 attestations(
3 where: {
4 recipient: { equals: "${getAddress(ethAddr)}" }
5 schemaId: { equals: "0xfa2eff59a916e3cc3246f9aec5e0ca00874ae9d09e4678e5016006f07622f977" }
6 }
7 take: 1
8 ) {
9 data
10 id
11 attester
12 }
13}
Onyesha yote

schemaId (opens in a new tab) hii inajumuisha anwani ya barua pepe tu. Swali hili linauliza uthibitisho wa skimu hii. Mada ya uthibitisho inaitwa recipient. Daima ni anwani ya Ethereum.

Onyo: Njia tunayopata uthibitisho hapa ina masuala mawili ya usalama.

  • Tunaenda kwenye eneo la mwisho la API, https://optimism.easscan.org/graphql, ambalo ni sehemu ya kati. Tunaweza kupata sifa ya id na kisha kufanya utafutaji wa mnyororo ili kuthibitisha kwamba uthibitisho ni halisi, lakini eneo la mwisho la API bado linaweza kudhibiti uthibitisho kwa kutotuambia kuhusu wao.

    Tatizo hili si gumu kulitatua, tunaweza kuendesha sehemu yetu ya GraphQL na kupata uthibitisho kutoka kwa kumbukumbu za mnyororo, lakini hiyo ni ya kupita kiasi kwa madhumuni yetu.

  • Hatuangalii utambulisho wa mthibitishaji. Mtu yeyote anaweza kutupa habari za uongo. Katika utekelezaji wa ulimwengu halisi tungekuwa na seti ya wathibitishaji wanaoaminika na kuangalia tu uthibitisho wao.

Ili kuona hili likitendeka, simamisha IdP na SP zilizopo na endesha amri hizi:

1git checkout email-address
2pnpm install
3pnpm start

Kisha toa anwani yako ya barua pepe. Una njia mbili za kufanya hivyo:

  • Ingiza mkoba ukitumia ufunguo binafsi, na utumie ufunguo binafsi wa majaribio 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80.

  • Ongeza uthibitisho kwa anwani yako ya barua pepe:

    1. Vinjari skimu katika kivinjari cha uthibitisho (opens in a new tab).

    2. Bofya Thibitisha na Schema.

    3. Ingiza anwani yako ya Ethereum kama mpokeaji, anwani yako ya barua pepe kama anwani ya barua pepe, na uchague Onchain. Kisha bofya Fanya Uthibitisho.

    4. Idhinisha muamala kwenye mkoba wako. Utahitaji ETH kwenye Mnyororo wa bloku wa Optimism (opens in a new tab) kulipia gesi.

Vyovyote vile, baada ya kufanya hivi nenda kwenye http://localhost:3000 (opens in a new tab) na ufuate maelekezo. Ikiwa umeingiza ufunguo binafsi wa majaribio, barua pepe unayopokea ni test_addr_0@example.com. Ikiwa umetumia anwani yako mwenyewe, inapaswa kuwa chochote ulichothibitisha.

Maelezo ya kina

Kupata kutoka anwani ya Ethereum hadi barua-pepe

Hatua mpya ni mawasiliano ya GraphQL, hatua 5.6 na 5.7.

Tena, hapa kuna sehemu zilizobadilishwa za idp.mts.

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

Ingiza maktaba tunazohitaji.

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

Kuna sehemu tofauti kwa kila mnyororo wa bloku (opens in a new tab).

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

Unda mteja mpya wa GraphQLClient tunaoweza kutumia kuuliza maswali kwenye endpoint.

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

GraphQL inatupa tu kitu cha data kisichoeleweka na baiti. Ili kuielewa tunahitaji skimu.

1const ethereumAddressToEmail = async ethAddr => {

Kazi ya kupata kutoka anwani ya Ethereum hadi anwani ya barua-pepe.

1 const query = `
2 query GetAttestationsByRecipient {

Hili ni swali la GraphQL.

1 attestations(

Tunatafuta uthibitisho.

1 where: {
2 recipient: { equals: "${getAddress(ethAddr)}" }
3 schemaId: { equals: "0xfa2eff59a916e3cc3246f9aec5e0ca00874ae9d09e4678e5016006f07622f977" }
4 }

Uthibitisho tunaotaka ni ule ulio katika skimu yetu, ambapo mpokeaji ni getAddress(ethAddr). Kazi ya getAddress (opens in a new tab) inahakikisha anwani yetu ina checksum (opens in a new tab) sahihi. Hii ni muhimu kuhusu GraphQL ni muhimu kwa herufi kubwa na ndogo. "0xBAD060A7", "0xBad060A7", na "0xbad060a7" ni maadili tofauti.

1 take: 1

Bila kujali ni ithibati ngapi tunapata, tunataka tu ya kwanza.

1 ) {
2 data
3 id
4 attester
5 }
6 }`

Sehemu tunazotaka kupokea.

  • attester: Anwani iliyowasilisha uthibitisho. Kawaida hii hutumika kuamua kama kuamini uthibitisho au la.
  • id: Kitambulisho cha uthibitisho. Unaweza kutumia thamani hii kusoma uthibitisho onchain (opens in a new tab) ili kuthibitisha kuwa taarifa kutoka kwa swali la GraphQL ni sahihi.
  • data: Data ya skimu (katika kesi hii, anwani ya barua pepe).
1 const queryResult = await graphqlClient.request(query)
2
3 if (queryResult.attestations.length == 0)
4 return "no_address@available.is"

Ikiwa hakuna uthibitisho, rudisha thamani ambayo ni wazi si sahihi, lakini ambayo itaonekana kuwa halali kwa mtoa huduma.

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

Ikiwa kuna thamani, tumia decodeData kusimbua data. Hatuhitaji metadata inayotoa, ni thamani yenyewe tu.

1 const loginResponse = await idp.createLoginResponse(
2 sp,
3 {
4 .
5 .
6 .
7 },
8 "post",
9 {
10 email: await ethereumAddressToEmail(req.params.account)
11 }
12 );
Onyesha yote

Tumia kazi mpya kupata anwani ya barua pepe.

Vipi kuhusu utawanyaji wa mamlaka?

Katika usanidi huu watumiaji hawawezi kujifanya kuwa mtu mwingine, mradi tu tunategemea wathibitishaji wanaoaminika kwa ramani ya anwani ya Ethereum hadi barua pepe. Hata hivyo, mtoa huduma wetu wa kitambulisho bado ni sehemu ya kati. Yeyote aliye na ufunguo binafsi wa mtoa huduma wa kitambulisho anaweza kutuma habari za uongo kwa mtoa huduma.

Kunaweza kuwa na suluhisho kwa kutumia hesabu ya pande nyingi (MPC) (opens in a new tab). Natumai kuandika kuihusu katika mafunzo yajayo.

Hitimisho

Kukubalika kwa kiwango cha kuingia, kama vile saini za Ethereum, kunakabiliwa na tatizo la kuku na yai. Watoa huduma wanataka kuvutia soko pana iwezekanavyo. Watumiaji wanataka kuwa na uwezo wa kufikia huduma bila kuwa na wasiwasi kuhusu kuunga mkono kiwango chao cha kuingia. Kuunda adapta, kama vile Ethereum IdP, kunaweza kutusaidia kushinda kikwazo hiki.

Tazama hapa kwa kazi zangu zaidi (opens in a new tab).

Ukurasa ulihaririwa mwisho: 23 Novemba 2025

Umesaidika na mafunzo haya?