వెబ్2 ప్రామాణీకరణ కోసం ఎథీరియంను ఉపయోగించడం
పరిచయం
SAML (opens in a new tab) అనేది [సర్వీస్ ప్రొవైడర్ల (SP)](https://en.wikipedia.org/wiki/Service_provider_(SAML) (opens in a new tab) కోసం వినియోగదారు సమాచారాన్ని అందించడానికి ఐడెంటిటీ ప్రొవైడర్ను (IdP) (opens in a new tab) అనుమతించడానికి వెబ్2లో ఉపయోగించే ఒక ప్రమాణం.
ఈ ట్యుటోరియల్లో మీరు ఎథీరియం సంతకాలను SAMLతో ఎలా అనుసంధానించాలో నేర్చుకుంటారు, తద్వారా వినియోగదారులు తమ ఎథీరియం వాలెట్లను ఉపయోగించి ఇంకా ఎథీరియంకు స్థానికంగా మద్దతు ఇవ్వని వెబ్2 సేవలకు తమను తాము ప్రామాణీకరించుకోవచ్చు.
ఈ ట్యుటోరియల్ రెండు వేర్వేరు ప్రేక్షకుల కోసం వ్రాయబడిందని గమనించండి:
- ఎథీరియంను అర్థం చేసుకుని, SAML నేర్చుకోవాల్సిన ఎథీరియం వ్యక్తులు
- SAML మరియు వెబ్2 ప్రామాణీకరణను అర్థం చేసుకుని, ఎథీరియం నేర్చుకోవాల్సిన వెబ్2 వ్యక్తులు
ఫలితంగా, ఇది మీకు ఇప్పటికే తెలిసిన చాలా పరిచయ విషయాలను కలిగి ఉంటుంది. దాన్ని దాటవేయడానికి సంకోచించకండి.
ఎథీరియం వ్యక్తుల కోసం SAML
SAML అనేది ఒక కేంద్రీకృత ప్రోటోకాల్. ఒక సర్వీస్ ప్రొవైడర్ (SP) ఐడెంటిటీ ప్రొవైడర్ (IdP) నుండి నిర్ధారణలను ("ఇది నా వినియోగదారు జాన్, అతనికి A, B మరియు C చేయడానికి అనుమతులు ఉండాలి" వంటివి) అంగీకరిస్తుంది, దానికి దానితో లేదా ఆ IdP సర్టిఫికెట్పై సంతకం చేసిన సర్టిఫికెట్ అథారిటీతో (opens in a new tab) ముందుగా ఉన్న నమ్మక సంబంధం ఉంటే మాత్రమే.
ఉదాహరణకు, SP అనేది కంపెనీలకు ప్రయాణ సేవలను అందించే ట్రావెల్ ఏజెన్సీ కావచ్చు మరియు IdP అనేది కంపెనీ అంతర్గత వెబ్సైట్ కావచ్చు. ఉద్యోగులు వ్యాపార ప్రయాణాన్ని బుక్ చేసుకోవలసి వచ్చినప్పుడు, ట్రావెల్ ఏజెన్సీ వారిని వాస్తవానికి ప్రయాణాన్ని బుక్ చేసుకోవడానికి అనుమతించే ముందు కంపెనీ ద్వారా ప్రామాణీకరణ కోసం పంపుతుంది.
బ్రౌజర్, SP మరియు IdP అనే మూడు ఎంటిటీలు యాక్సెస్ కోసం చర్చలు జరిపే విధానం ఇది. బ్రౌజర్ను ఉపయోగిస్తున్న వినియోగదారు గురించి SP ముందుగా ఏమీ తెలుసుకోవలసిన అవసరం లేదు, కేవలం IdPని విశ్వసిస్తే సరిపోతుంది.
SAML వ్యక్తుల కోసం ఎథీరియం
ఎథీరియం అనేది ఒక వికేంద్రీకృత వ్యవస్థ.
వినియోగదారులు ఒక ప్రైవేట్ కీని కలిగి ఉంటారు (సాధారణంగా బ్రౌజర్ ఎక్స్టెన్షన్లో ఉంచబడుతుంది). ప్రైవేట్ కీ నుండి మీరు పబ్లిక్ కీని పొందవచ్చు మరియు దాని నుండి 20-బైట్ల చిరునామాను పొందవచ్చు. వినియోగదారులు సిస్టమ్లోకి లాగిన్ అవ్వవలసి వచ్చినప్పుడు, నాన్స్తో (ఒకేసారి ఉపయోగించే విలువ) సందేశంపై సంతకం చేయమని వారిని అభ్యర్థిస్తారు. ఆ చిరునామా ద్వారా సంతకం సృష్టించబడిందని సర్వర్ ధృవీకరించగలదు.
సంతకం ఎథీరియం చిరునామాను మాత్రమే ధృవీకరిస్తుంది. ఇతర వినియోగదారు లక్షణాలను పొందడానికి, మీరు సాధారణంగా ధృవీకరణలను (opens in a new tab) ఉపయోగిస్తారు. ఒక ధృవీకరణ సాధారణంగా ఈ ఫీల్డ్లను కలిగి ఉంటుంది:
- అటెస్టర్ (Attestor), ధృవీకరణ చేసిన చిరునామా
- స్వీకర్త (Recipient), ధృవీకరణ వర్తించే చిరునామా
- డేటా (Data), పేరు, అనుమతులు మొదలైన ధృవీకరించబడుతున్న డేటా.
- స్కీమా (Schema), డేటాను అర్థం చేసుకోవడానికి ఉపయోగించే స్కీమా యొక్క ID.
ఎథీరియం యొక్క వికేంద్రీకృత స్వభావం కారణంగా, ఏ వినియోగదారుడైనా ధృవీకరణలు చేయవచ్చు. ఏ ధృవీకరణలను మనం నమ్మదగినవిగా పరిగణిస్తామో గుర్తించడానికి అటెస్టర్ గుర్తింపు ముఖ్యం.
సెటప్
మొదటి దశ SAML SP మరియు SAML IdP తమ మధ్య కమ్యూనికేట్ చేసుకోవడం.
-
సాఫ్ట్వేర్ను డౌన్లోడ్ చేయండి. ఈ వ్యాసం కోసం నమూనా సాఫ్ట్వేర్ 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 -
స్వీయ-సంతకం చేసిన సర్టిఫికెట్లతో కీలను సృష్టించండి. దీని అర్థం కీ దాని స్వంత సర్టిఫికెట్ అథారిటీ, మరియు సర్వీస్ ప్రొవైడర్కు మాన్యువల్గా దిగుమతి చేయబడాలి. మరింత సమాచారం కోసం 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 .. -
సర్వర్లను ప్రారంభించండి (SP మరియు IdP రెండూ)
pnpm start -
URL http://localhost:3000/ (opens in a new tab) వద్ద SPకి బ్రౌజ్ చేయండి మరియు IdP (పోర్ట్ 3001)కి దారి మళ్లించబడటానికి బటన్ను క్లిక్ చేయండి.
-
IdPకి మీ ఇమెయిల్ చిరునామాను అందించండి మరియు సర్వీస్ ప్రొవైడర్కు లాగిన్ చేయి (Login to the service provider) క్లిక్ చేయండి. మీరు తిరిగి సర్వీస్ ప్రొవైడర్కు (పోర్ట్ 3000) దారి మళ్లించబడ్డారని మరియు అది మీ ఇమెయిల్ చిరునామా ద్వారా మిమ్మల్ని గుర్తిస్తుందని చూడండి.
వివరణాత్మక వివరణ
దశలవారీగా ఏమి జరుగుతుందో ఇక్కడ ఉంది:
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()
పబ్లిక్ కీలను చదవండి, ఇవి సాధారణంగా రెండు భాగాలకు అందుబాటులో ఉంటాయి (మరియు నేరుగా విశ్వసించబడతాయి లేదా విశ్వసనీయ సర్టిఫికెట్ అథారిటీ ద్వారా సంతకం చేయబడతాయి).
export const spPort = 3000
export const spHostname = "localhost"
export const spDir = "sp"
export const idpPort = 3001
export const idpHostname = "localhost"
export const idpDir = "idp"
export const spUrl = `${protocol}://${spHostname}:${spPort}/${spDir}`
export const idpUrl = `${protocol}://${idpHostname}:${idpPort}/${idpDir}`
రెండు భాగాల కోసం URLలు.
export const spPublicData = {
సర్వీస్ ప్రొవైడర్ కోసం పబ్లిక్ డేటా.
entityID: `${spUrl}/metadata`,
సాంప్రదాయం ప్రకారం, SAMLలో entityID అనేది ఎంటిటీ యొక్క మెటాడేటా అందుబాటులో ఉండే URL. ఈ మెటాడేటా ఇక్కడ పబ్లిక్ డేటాకు అనుగుణంగా ఉంటుంది, అయితే ఇది XML రూపంలో ఉంటుంది.
wantAssertionsSigned: true,
authnRequestsSigned: false,
signingCert: spCert,
allowCreate: true,
assertionConsumerService: [{
Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
Location: `${spUrl}/assertion`,
}]
}
మన ప్రయోజనాల కోసం అత్యంత ముఖ్యమైన నిర్వచనం assertionConsumerServer. దీని అర్థం సర్వీస్ ప్రొవైడర్కు ఏదైనా నిర్ధారించడానికి (ఉదాహరణకు, "మీకు ఈ సమాచారాన్ని పంపే వినియోగదారు somebody@example.com (opens email client)") మనం URL http://localhost:3000/sp/assertionకి HTTP POST (opens in a new tab)ని ఉపయోగించాలి.
export const idpPublicData = {
entityID: `${idpUrl}/metadata`,
signingCert: idpCert,
wantAuthnRequestsSigned: false,
singleSignOnService: [{
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
Location: `${idpUrl}/login`
}],
singleLogoutService: [{
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
Location: `${idpUrl}/logout`
}],
}
ఐడెంటిటీ ప్రొవైడర్ కోసం పబ్లిక్ డేటా కూడా ఇలాగే ఉంటుంది. వినియోగదారుని లాగిన్ చేయడానికి మీరు http://localhost:3001/idp/loginకి POST చేయాలని మరియు వినియోగదారుని లాగ్ అవుట్ చేయడానికి మీరు http://localhost:3001/idp/logoutకి POST చేయాలని ఇది నిర్దేశిస్తుంది.
src/sp.mts
ఇది సర్వీస్ ప్రొవైడర్ను అమలు చేసే కోడ్.
import * as config from "./config.mts"
const fs = await import("fs")
const saml = await import("samlify")
SAMLని అమలు చేయడానికి మనం samlify (opens in a new tab) లైబ్రరీని ఉపయోగిస్తాము.
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()
ఒక express (opens in a new tab) Router (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 భాగాలతో పరస్పర అనుసంధానతను ప్రారంభించడానికి, సర్వీస్ మరియు ఐడెంటిటీ ప్రొవైడర్లు తమ పబ్లిక్ డేటాను (మెటాడేటా అని పిలుస్తారు) /metadataలో XML ఆకృతిలో అందుబాటులో ఉంచాలి.
spRouter.post(`/assertion`,
ఇది తనను తాను గుర్తించుకోవడానికి బ్రౌజర్ యాక్సెస్ చేసే పేజీ. నిర్ధారణలో వినియోగదారు ఐడెంటిఫైయర్ (ఇక్కడ మనం ఇమెయిల్ చిరునామాను ఉపయోగిస్తాము) ఉంటుంది మరియు అదనపు లక్షణాలను చేర్చవచ్చు. ఇది పై సీక్వెన్స్ రేఖాచిత్రంలోని 7వ దశకు హ్యాండ్లర్.
async (req, res) => {
// console.log(`SAML ప్రతిస్పందన:\n${Buffer.from(req.body.SAMLResponse, 'base64').toString('utf-8')}`)
నిర్ధారణలో అందించబడిన XML డేటాను చూడటానికి మీరు కామెంట్ చేయబడిన కమాండ్ను ఉపయోగించవచ్చు. ఇది బేస్64 ఎన్కోడ్ (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")
లాగిన్ అభ్యర్థనను పోస్ట్ చేయడానికి సమాచారాన్ని పొందండి.
res.send(`
<html>
<body>
<script>
window.onload = function () { document.forms[0].submit(); }
</script>
ఈ పేజీ ఫారమ్ను (క్రింద చూడండి) స్వయంచాలకంగా సమర్పిస్తుంది. ఈ విధంగా వినియోగదారు దారి మళ్లించబడటానికి ఏమీ చేయవలసిన అవసరం లేదు. ఇది పై సీక్వెన్స్ రేఖాచిత్రంలోని 2వ దశ.
<form method="post" action="${loginRequest.entityEndpoint}">
loginRequest.entityEndpointకి (ఐడెంటిటీ ప్రొవైడర్ ఎండ్పాయింట్ యొక్క URL) పోస్ట్ చేయండి.
<input type="hidden" name="${loginRequest.type}" value="${loginRequest.context}" />
ఇన్పుట్ పేరు loginRequest.type (SAMLRequest). ఆ ఫీల్డ్ కంటెంట్ loginRequest.context, ఇది మళ్లీ బేస్64 ఎన్కోడ్ చేయబడిన XML.
</form>
</body>
</html>
`)
}
)
app.use(express.urlencoded({extended: true}))
ఈ మిడిల్వేర్ (opens in a new tab) HTTP అభ్యర్థన (opens in a new tab) యొక్క బాడీని చదువుతుంది. అప్రమేయంగా ఎక్స్ప్రెస్ దీన్ని విస్మరిస్తుంది, ఎందుకంటే చాలా అభ్యర్థనలకు ఇది అవసరం లేదు. POST బాడీని ఉపయోగిస్తుంది కాబట్టి మనకు ఇది అవసరం.
app.use(`/${config.spDir}`, spRouter)
సర్వీస్ ప్రొవైడర్ డైరెక్టరీలో (/sp) రౌటర్ను మౌంట్ చేయండి.
app.get("/", (req, res) => {
res.send(`
<html>
<body>
<button onClick="document.location.href='${config.spUrl}/login'">
Click here to log on
</button>
</body>
</html>
`)
})
బ్రౌజర్ రూట్ డైరెక్టరీని పొందడానికి ప్రయత్నిస్తే, దానికి లాగిన్ పేజీకి లింక్ను అందించండి.
app.listen(config.spPort, () => {
console.log(`service provider is running on http://${config.spHostname}:${config.spPort}`)
})
ఈ ఎక్స్ప్రెస్ అప్లికేషన్తో spPortని వినండి.
src/idp.mts
ఇది ఐడెంటిటీ ప్రొవైడర్. ఇది సర్వీస్ ప్రొవైడర్కు చాలా పోలి ఉంటుంది, క్రింది వివరణలు భిన్నంగా ఉన్న భాగాల కోసం.
const xmlParser = new (await import("fast-xml-parser")).XMLParser(
{
ignoreAttributes: false, // లక్షణాలను భద్రపరచండి
attributeNamePrefix: "@_", // లక్షణాల కోసం ఉపసర్గ
}
)
సర్వీస్ ప్రొవైడర్ నుండి మనం స్వీకరించే XML అభ్యర్థనను మనం చదివి అర్థం చేసుకోవాలి.
const getLoginPage = requestId => `
ఈ ఫంక్షన్ పై సీక్వెన్స్ రేఖాచిత్రంలోని 4వ దశలో తిరిగి ఇవ్వబడిన ఆటో-సబ్మిట్ చేయబడిన ఫారమ్తో పేజీని సృష్టిస్తుంది.
<html>
<head>
<title>Login page</title>
</head>
<body>
<h2>Login page</h2>
<form method="post" action="./loginSubmitted">
<input type="hidden" name="requestId" value="${requestId}" />
Email address: <input name="email" />
<br />
<button type="Submit">
Login to the service provider
</button>
మనం సర్వీస్ ప్రొవైడర్కు పంపే రెండు ఫీల్డ్లు ఉన్నాయి:
- మనం ప్రతిస్పందిస్తున్న
requestId. - వినియోగదారు ఐడెంటిఫైయర్ (ప్రస్తుతానికి వినియోగదారు అందించే ఇమెయిల్ చిరునామాను మనం ఉపయోగిస్తాము).
</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
ఇది మనం సర్వీస్ ప్రొవైడర్కు తిరిగి పంపే వినియోగదారు సమాచారంతో ఉన్న ఫీల్డ్.
}
);
res.send(`
<html>
<body>
<script>
window.onload = function () { document.forms[0].submit(); }
</script>
<form method="post" action="${loginResponse.entityEndpoint}">
<input type="hidden" name="${loginResponse.type}" value="${loginResponse.context}" />
</form>
</body>
</html>
`)
})
మళ్లీ, ఆటో-సబ్మిట్ చేయబడిన ఫారమ్ను ఉపయోగించండి. ఇది పై సీక్వెన్స్ రేఖాచిత్రంలోని 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"]))
ప్రామాణీకరణ అభ్యర్థన యొక్క IDని చదవడానికి మనం idp.parseLoginRequest (opens in a new tab)ని ఉపయోగించగలగాలి. అయితే, నేను దాన్ని పని చేసేలా చేయలేకపోయాను మరియు దానిపై ఎక్కువ సమయం వెచ్చించడం విలువైనది కాదు కాబట్టి నేను కేవలం సాధారణ-ప్రయోజన XML పార్సర్ను (opens in a new tab) ఉపయోగిస్తాను. మనకు అవసరమైన సమాచారం <samlp:AuthnRequest> ట్యాగ్ లోపల ఉన్న ID లక్షణం, ఇది 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 దశల్లో మార్పులు ఉన్నాయి.
మనం మార్చిన ఏకైక ఫైల్ idp.mts. మార్చబడిన భాగాలు ఇక్కడ ఉన్నాయి.
import { v4 as uuidv4 } from 'uuid'
import { verifyMessage } from 'viem'
మనకు ఈ రెండు అదనపు లైబ్రరీలు అవసరం. నాన్స్ విలువను సృష్టించడానికి మనం uuid (opens in a new tab)ని ఉపయోగిస్తాము. విలువ ముఖ్యం కాదు, అది ఒక్కసారి మాత్రమే ఉపయోగించబడుతుందనే వాస్తవం ముఖ్యం.
viem (opens in a new tab) లైబ్రరీ ఎథీరియం నిర్వచనాలను ఉపయోగించడానికి మమ్మల్ని అనుమతిస్తుంది. సంతకం నిజంగా చెల్లుబాటు అవుతుందని ధృవీకరించడానికి ఇక్కడ మనకు ఇది అవసరం.
const loginPrompt = "To access the service provider, sign this nonce: "
సందేశంపై సంతకం చేయడానికి వాలెట్ వినియోగదారుని అనుమతి అడుగుతుంది. కేవలం నాన్స్ మాత్రమే ఉన్న సందేశం వినియోగదారులను గందరగోళానికి గురి చేస్తుంది, కాబట్టి మనం ఈ ప్రాంప్ట్ను చేర్చుతాము.
// requestIDలను ఇక్కడ ఉంచండి
let nonces = {}
అభ్యర్థనకు ప్రతిస్పందించడానికి మనకు అభ్యర్థన సమాచారం అవసరం. మనం దాన్ని అభ్యర్థనతో (దశ 4) పంపవచ్చు మరియు దాన్ని తిరిగి స్వీకరించవచ్చు (దశ 5). అయితే, బ్రౌజర్ నుండి మనకు లభించే సమాచారాన్ని మనం విశ్వసించలేము, ఇది హానికరమైన వినియోగదారు నియంత్రణలో ఉండవచ్చు. కాబట్టి నాన్స్ను కీగా ఉంచి, దాన్ని ఇక్కడే నిల్వ చేయడం మంచిది.
సరళత కోసం మనం దీన్ని ఇక్కడ వేరియబుల్గా చేస్తున్నామని గమనించండి. అయితే, దీనికి అనేక ప్రతికూలతలు ఉన్నాయి:
- మనం డినైయల్ ఆఫ్ సర్వీస్ దాడికి గురయ్యే ప్రమాదం ఉంది. హానికరమైన వినియోగదారు మన మెమరీని నింపుతూ, బహుళ సార్లు లాగిన్ అవ్వడానికి ప్రయత్నించవచ్చు.
- 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 ప్రాసెస్ చెడ్డ సంతకాలను సరిగ్గా నిర్వహిస్తుందో లేదో ధృవీకరించడం ముఖ్యం.
</script>
</head>
<body>
<h2>Please sign</h2>
<button onClick="window.goodSignature()">
Submit a good (valid) signature
</button>
<br/>
<button onClick="window.badSignature()">
Submit a bad (invalid) signature
</button>
</body>
</html>
`
}
మిగిలినది కేవలం ప్రామాణిక 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
})
సీక్వెన్స్ రేఖాచిత్రంలో 5.5వ దశను అమలు చేయడానికి verifyMessage (opens in a new tab)ని ఉపయోగించండి.
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"
}
);
మన వద్ద అసలు ఇమెయిల్ చిరునామా లేదు (మనం దాన్ని తదుపరి విభాగంలో పొందుతాము), కాబట్టి ప్రస్తుతానికి మనం ఎథీరియం చిరునామాను తిరిగి ఇస్తాము మరియు దాన్ని ఇమెయిల్ చిరునామా కాదని స్పష్టంగా గుర్తిస్తాము.
// లాగిన్ అభ్యర్థనల కోసం IdP ఎండ్పాయింట్
idpRouter.post(`/login`,
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(getSignaturePage(samlRequest["samlp:AuthnRequest"]["@_ID"]))
} catch (err) {
console.error('Error processing SAML response:', err);
res.status(400).send('SAML authentication failed');
}
}
)
getLoginPageకి బదులుగా, ఇప్పుడు 3వ దశ హ్యాండ్లర్లో getSignaturePageని ఉపయోగించండి.
ఇమెయిల్ చిరునామాను పొందడం
తదుపరి దశ ఇమెయిల్ చిరునామాను పొందడం, ఇది సర్వీస్ ప్రొవైడర్ అభ్యర్థించిన ఐడెంటిఫైయర్. అలా చేయడానికి, మనం ఎథీరియం అటెస్టేషన్ సర్వీస్ (EAS) (opens in a new tab)ని ఉపయోగిస్తాము.
ధృవీకరణలను పొందడానికి సులభమైన మార్గం GraphQL API (opens in a new tab)ని ఉపయోగించడం. మనం ఈ క్వెరీని ఉపయోగిస్తాము:
query GetAttestationsByRecipient {
attestations(
where: {
recipient: { equals: "${getAddress(ethAddr)}" }
schemaId: { equals: "0xfa2eff59a916e3cc3246f9aec5e0ca00874ae9d09e4678e5016006f07622f977" }
}
take: 1
) {
data
id
attester
}
}
ఈ 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ని ఉపయోగించండి. -
మీ స్వంత ఇమెయిల్ చిరునామా కోసం ధృవీకరణను జోడించండి:
-
అటెస్టేషన్ ఎక్స్ప్లోరర్లోని స్కీమాకు (opens in a new tab) బ్రౌజ్ చేయండి.
-
స్కీమాతో ధృవీకరించు (Attest with Schema) క్లిక్ చేయండి.
-
మీ ఎథీరియం చిరునామాను స్వీకర్తగా, మీ ఇమెయిల్ చిరునామాను ఇమెయిల్ చిరునామాగా నమోదు చేయండి మరియు ఆన్చైన్ (Onchain) ఎంచుకోండి. ఆపై ధృవీకరణ చేయి (Make Attestation) క్లిక్ చేయండి.
-
మీ వాలెట్లో లావాదేవీని ఆమోదించండి. గ్యాస్ చెల్లించడానికి మీకు Optimism బ్లాక్చైన్లో (opens in a new tab) కొంత ETH అవసరం.
-
ఏదైనా సరే, మీరు దీన్ని చేసిన తర్వాత http://localhost:3000 (opens in a new tab)కి బ్రౌజ్ చేయండి మరియు సూచనలను అనుసరించండి. మీరు టెస్టింగ్ ప్రైవేట్ కీని దిగుమతి చేసుకుంటే, మీరు స్వీకరించే ఇమెయిల్ test_addr_0@example.com. మీరు మీ స్వంత చిరునామాను ఉపయోగిస్తే, అది మీరు ధృవీకరించినది ఏదైనా అయి ఉండాలి.
వివరణాత్మక వివరణ
కొత్త దశలు 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. GraphQL క్వెరీ నుండి వచ్చిన సమాచారం సరైనదని ధృవీకరించడానికి ఆన్చైన్లో ధృవీకరణను చదవడానికి (opens in a new tab) మీరు ఈ విలువను ఉపయోగించవచ్చు.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ని ఉపయోగించండి. అది అందించే మెటాడేటా మనకు అవసరం లేదు, కేవలం విలువ మాత్రమే కావాలి.
const loginResponse = await idp.createLoginResponse(
sp,
{
.
.
.
},
"post",
{
email: await ethereumAddressToEmail(req.params.account)
}
);
ఇమెయిల్ చిరునామాను పొందడానికి కొత్త ఫంక్షన్ను ఉపయోగించండి.
వికేంద్రీకరణ గురించి ఏమిటి?
ఈ కాన్ఫిగరేషన్లో, ఎథీరియం నుండి ఇమెయిల్ చిరునామా మ్యాపింగ్ కోసం మనం నమ్మదగిన అటెస్టర్లపై ఆధారపడినంత కాలం, వినియోగదారులు తాము కాని వారిలా నటించలేరు. అయితే, మన ఐడెంటిటీ ప్రొవైడర్ ఇప్పటికీ ఒక కేంద్రీకృత భాగం. ఐడెంటిటీ ప్రొవైడర్ యొక్క ప్రైవేట్ కీ ఎవరి వద్ద ఉంటే వారు సర్వీస్ ప్రొవైడర్కు తప్పుడు సమాచారాన్ని పంపగలరు.
మల్టీ-పార్టీ కంప్యూటేషన్ (MPC) (opens in a new tab) ఉపయోగించి ఒక పరిష్కారం ఉండవచ్చు. భవిష్యత్తు ట్యుటోరియల్లో దీని గురించి వ్రాయాలని నేను ఆశిస్తున్నాను.
ముగింపు
ఎథీరియం సంతకాల వంటి లాగ్ ఆన్ ప్రమాణాన్ని స్వీకరించడం కోడి మరియు గుడ్డు సమస్యను ఎదుర్కొంటుంది. సర్వీస్ ప్రొవైడర్లు సాధ్యమైనంత విస్తృతమైన మార్కెట్ను ఆకర్షించాలనుకుంటున్నారు. వినియోగదారులు తమ లాగ్ ఆన్ ప్రమాణానికి మద్దతు ఇవ్వడం గురించి ఆందోళన చెందకుండా సేవలను యాక్సెస్ చేయగలగాలి అని కోరుకుంటారు. ఎథీరియం IdP వంటి ఎడాప్టర్లను సృష్టించడం ఈ అడ్డంకిని అధిగమించడానికి మనకు సహాయపడుతుంది.





