ویب 2 کی تصدیق کے لیے ایتھیریم کا استعمال
تعارف
SAML (opens in a new tab) ویب 2 پر استعمال ہونے والا ایک معیار ہے جو ایک شناختی فراہم کنندہ (IdP) (opens in a new tab) کو سروس فراہم کنندگان (SP) (opens in a new tab) کے لیے صارف کی معلومات فراہم کرنے کی اجازت دیتا ہے۔
اس ٹیوٹوریل میں آپ سیکھیں گے کہ ایتھیریم سگنیچرز کو SAML کے ساتھ کیسے مربوط کیا جائے تاکہ صارفین اپنے ایتھیریم والیٹس کا استعمال کرتے ہوئے خود کو ان ویب 2 سروسز پر تصدیق کر سکیں جو ابھی تک مقامی طور پر ایتھیریم کو سپورٹ نہیں کرتیں۔
نوٹ کریں کہ یہ ٹیوٹوریل دو الگ الگ سامعین کے لیے لکھا گیا ہے:
- ایتھیریم کے لوگ جو ایتھیریم کو سمجھتے ہیں اور انہیں SAML سیکھنے کی ضرورت ہے
- ویب 2 کے لوگ جو SAML اور ویب 2 کی تصدیق کو سمجھتے ہیں اور انہیں ایتھیریم سیکھنے کی ضرورت ہے
نتیجتاً، اس میں بہت سا تعارفی مواد شامل ہونے والا ہے جو آپ پہلے سے جانتے ہیں۔ آپ اسے بلا جھجھک چھوڑ سکتے ہیں۔
ایتھیریم کے لوگوں کے لیے SAML
SAML ایک مرکزی (centralized) پروٹوکول ہے۔ ایک سروس فراہم کنندہ (SP) شناختی فراہم کنندہ (IdP) سے صرف اسی صورت میں دعوے (assertions) قبول کرتا ہے (جیسے "یہ میرا صارف جان ہے، اسے A، B، اور C کرنے کی اجازت ہونی چاہیے") اگر اس کا اس کے ساتھ، یا اس سرٹیفکیٹ اتھارٹی (opens in a new tab) کے ساتھ پہلے سے موجود اعتماد کا رشتہ ہو جس نے اس IdP کے سرٹیفکیٹ پر دستخط کیے ہوں۔
مثال کے طور پر، SP ایک ٹریول ایجنسی ہو سکتی ہے جو کمپنیوں کو سفری خدمات فراہم کرتی ہے، اور IdP کسی کمپنی کی اندرونی ویب سائٹ ہو سکتی ہے۔ جب ملازمین کو کاروباری سفر بک کرنے کی ضرورت ہوتی ہے، تو ٹریول ایجنسی انہیں اصل میں سفر بک کرنے دینے سے پہلے کمپنی کے ذریعے تصدیق کے لیے بھیجتی ہے۔
یہ وہ طریقہ ہے جس سے تینوں ادارے، براؤزر، SP، اور IdP، رسائی کے لیے بات چیت کرتے ہیں۔ SP کو براؤزر استعمال کرنے والے صارف کے بارے میں پہلے سے کچھ جاننے کی ضرورت نہیں ہے، بس IdP پر بھروسہ کرنا ہوتا ہے۔
SAML کے لوگوں کے لیے ایتھیریم
ایتھیریم ایک غیر مرکزی (decentralized) نظام ہے۔
صارفین کے پاس ایک پرائیویٹ کلید (private key) ہوتی ہے (جو عام طور پر براؤزر ایکسٹینشن میں رکھی جاتی ہے)۔ پرائیویٹ کلید سے آپ ایک پبلک کلید (public key) اخذ کر سکتے ہیں، اور اس سے 20 بائٹ کا ایڈریس۔ جب صارفین کو کسی سسٹم میں لاگ ان کرنے کی ضرورت ہوتی ہے، تو ان سے ایک نانس (nonce - ایک بار استعمال ہونے والی قدر) کے ساتھ پیغام پر دستخط کرنے کی درخواست کی جاتی ہے۔ سرور تصدیق کر سکتا ہے کہ دستخط اسی ایڈریس کے ذریعے بنائے گئے تھے۔
دستخط صرف ایتھیریم ایڈریس کی تصدیق کرتا ہے۔ صارف کی دیگر خصوصیات حاصل کرنے کے لیے، آپ عام طور پر تصدیق نامے (attestations) (opens in a new tab) استعمال کرتے ہیں۔ ایک تصدیق نامے میں عام طور پر یہ فیلڈز ہوتے ہیں:
- تصدیق کنندہ (Attestor)، وہ ایڈریس جس نے تصدیق کی
- وصول کنندہ (Recipient)، وہ ایڈریس جس پر تصدیق کا اطلاق ہوتا ہے
- ڈیٹا (Data)، وہ ڈیٹا جس کی تصدیق کی جا رہی ہے، جیسے نام، اجازتیں وغیرہ۔
- اسکیما (Schema)، اسکیما کی ID جو ڈیٹا کی تشریح کے لیے استعمال ہوتی ہے۔
ایتھیریم کی غیر مرکزی نوعیت کی وجہ سے، کوئی بھی صارف تصدیق کر سکتا ہے۔ تصدیق کنندہ کی شناخت یہ پہچاننے کے لیے اہم ہے کہ ہم کن تصدیق ناموں کو قابل اعتماد سمجھتے ہیں۔
سیٹ اپ
پہلا قدم یہ ہے کہ ایک SAML SP اور ایک SAML IdP آپس میں بات چیت کر رہے ہوں۔
-
سافٹ ویئر ڈاؤن لوڈ کریں۔ اس مضمون کے لیے نمونہ سافٹ ویئر گٹ ہب پر (opens in a new tab) موجود ہے۔ مختلف مراحل مختلف برانچز میں محفوظ ہیں، اس مرحلے کے لیے آپ کو
saml-onlyدرکار ہے۔1git clone https://github.com/qbzzt/250420-saml-ethereum -b saml-only2cd 250420-saml-ethereum3pnpm install -
سیلف سائنڈ (self-signed) سرٹیفکیٹس کے ساتھ کلیدیں بنائیں۔ اس کا مطلب ہے کہ کلید اپنی خود کی سرٹیفکیٹ اتھارٹی ہے، اور اسے سروس فراہم کنندہ میں دستی طور پر امپورٹ کرنے کی ضرورت ہے۔ مزید معلومات کے لیے OpenSSL کی دستاویزات (opens in a new tab) دیکھیں۔
1mkdir keys2cd keys3openssl 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 .. -
سرورز شروع کریں (SP اور IdP دونوں)
1pnpm start -
URL http://localhost:3000/ (opens in a new tab) پر SP کو براؤز کریں اور IdP (پورٹ 3001) پر ری ڈائریکٹ ہونے کے لیے بٹن پر کلک کریں۔
-
IdP کو اپنا ای میل ایڈریس فراہم کریں اور Login to the service provider پر کلک کریں۔ دیکھیں کہ آپ واپس سروس فراہم کنندہ (پورٹ 3000) پر ری ڈائریکٹ ہو جاتے ہیں اور یہ آپ کو آپ کے ای میل ایڈریس سے پہچانتا ہے۔
تفصیلی وضاحت
مرحلہ وار یہ ہوتا ہے:
src/config.mts
اس فائل میں شناختی فراہم کنندہ (Identity Provider) اور سروس فراہم کنندہ (Service Provider) دونوں کی کنفیگریشن شامل ہے۔ عام طور پر یہ دونوں مختلف ادارے ہوں گے، لیکن یہاں ہم سادگی کے لیے کوڈ شیئر کر سکتے ہیں۔
1const fs = await import("fs")23const protocol="http"فی الحال ہم صرف ٹیسٹنگ کر رہے ہیں، اس لیے HTTP کا استعمال ٹھیک ہے۔
1export const spCert = fs.readFileSync("keys/saml-sp.crt").toString()2export const idpCert = fs.readFileSync("keys/saml-idp.crt").toString()پبلک کلیدیں پڑھیں، جو عام طور پر دونوں اجزاء کو دستیاب ہوتی ہیں (اور یا تو براہ راست قابل اعتماد ہوتی ہیں، یا کسی قابل اعتماد سرٹیفکیٹ اتھارٹی کے ذریعے دستخط شدہ ہوتی ہیں)۔
1export const spPort = 30002export const spHostname = "localhost"3export const spDir = "sp"45export const idpPort = 30016export const idpHostname = "localhost"7export const idpDir = "idp"89export const spUrl = `${protocol}://${spHostname}:${spPort}/${spDir}`10export const idpUrl = `${protocol}://${idpHostname}:${idpPort}/${idpDir}`سب دکھائیںدونوں اجزاء کے URLs۔
1export const spPublicData = {سروس فراہم کنندہ کے لیے پبلک ڈیٹا۔
1 entityID: `${spUrl}/metadata`,روایت کے مطابق، SAML میں entityID وہ URL ہے جہاں ادارے کا میٹا ڈیٹا دستیاب ہوتا ہے۔ یہ میٹا ڈیٹا یہاں موجود پبلک ڈیٹا سے مطابقت رکھتا ہے، سوائے اس کے کہ یہ 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 }سب دکھائیںہمارے مقاصد کے لیے سب سے اہم تعریف assertionConsumerServer ہے۔ اس کا مطلب ہے کہ سروس فراہم کنندہ کے سامنے کچھ دعویٰ کرنے کے لیے (مثال کے طور پر، "جو صارف آپ کو یہ معلومات بھیجتا ہے وہ somebody@example.com (opens email client) ہے") ہمیں URL http://localhost:3000/sp/assertion پر HTTP POST (opens in a new tab) استعمال کرنے کی ضرورت ہے۔
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 }سب دکھائیںشناختی فراہم کنندہ کا پبلک ڈیٹا بھی اسی طرح کا ہے۔ یہ بتاتا ہے کہ صارف کو لاگ ان کرنے کے لیے آپ http://localhost:3001/idp/login پر POST کرتے ہیں اور صارف کو لاگ آؤٹ کرنے کے لیے آپ http://localhost:3001/idp/logout پر POST کرتے ہیں۔
src/sp.mts
یہ وہ کوڈ ہے جو سروس فراہم کنندہ کو نافذ کرتا ہے۔
1import * as config from "./config.mts"2const fs = await import("fs")3const saml = await import("samlify")ہم SAML کو نافذ کرنے کے لیے samlify (opens in a new tab) لائبریری کا استعمال کرتے ہیں۔
1import * as validator from "@authenio/samlify-node-xmllint"2saml.setSchemaValidator(validator)samlify لائبریری توقع کرتی ہے کہ ایک پیکیج اس بات کی توثیق کرے گا کہ XML درست ہے، متوقع پبلک کلید کے ساتھ دستخط شدہ ہے، وغیرہ۔ ہم اس مقصد کے لیے @authenio/samlify-node-xmllint (opens in a new tab) استعمال کرتے ہیں۔
1const express = (await import("express")).default2const spRouter = express.Router()3const app = express()ایک express (opens in a new tab) Router (opens in a new tab) ایک "منی ویب سائٹ" ہے جسے کسی ویب سائٹ کے اندر ماؤنٹ کیا جا سکتا ہے۔ اس صورت میں، ہم اسے سروس فراہم کنندہ کی تمام تعریفوں کو ایک ساتھ گروپ کرنے کے لیے استعمال کرتے ہیں۔
1const spPrivateKey = fs.readFileSync("keys/saml-sp.pem").toString()23const sp = saml.ServiceProvider({4 privateKey: spPrivateKey, 5 ...config.spPublicData6})سروس فراہم کنندہ کی اپنی نمائندگی وہ تمام پبلک ڈیٹا ہے، اور وہ پرائیویٹ کلید ہے جسے وہ معلومات پر دستخط کرنے کے لیے استعمال کرتا ہے۔
1const idp = saml.IdentityProvider(config.idpPublicData);پبلک ڈیٹا میں وہ سب کچھ شامل ہوتا ہے جو سروس فراہم کنندہ کو شناختی فراہم کنندہ کے بارے میں جاننے کی ضرورت ہوتی ہے۔
1spRouter.get(`/metadata`, 2 (req, res) => res.header("Content-Type", "text/xml").send(sp.getMetadata())3)دیگر SAML اجزاء کے ساتھ انٹرآپریبلٹی (interoperability) کو فعال کرنے کے لیے، سروس اور شناختی فراہم کنندگان کا پبلک ڈیٹا (جسے میٹا ڈیٹا کہا جاتا ہے) /metadata میں XML فارمیٹ میں دستیاب ہونا چاہیے۔
1spRouter.post(`/assertion`,یہ وہ صفحہ ہے جس تک براؤزر اپنی شناخت کے لیے رسائی حاصل کرتا ہے۔ دعوے (assertion) میں صارف کا شناخت کنندہ (یہاں ہم ای میل ایڈریس استعمال کرتے ہیں) شامل ہوتا ہے، اور اس میں اضافی خصوصیات شامل ہو سکتی ہیں۔ یہ اوپر دی گئی ترتیب کے خاکہ (sequence diagram) میں مرحلہ 7 کا ہینڈلر ہے۔
1 async (req, res) => {2 // console.log(`SAML response:\n${Buffer.from(req.body.SAMLResponse, 'base64').toString('utf-8')}`)آپ دعوے میں فراہم کردہ XML ڈیٹا دیکھنے کے لیے کمنٹ آؤٹ کی گئی کمانڈ استعمال کر سکتے ہیں۔ یہ base64 انکوڈ شدہ (opens in a new tab) ہے۔
1 try {2 const loginResponse = await sp.parseLoginResponse(idp, 'post', req);شناختی سرور سے لاگ ان کی درخواست کو پارس (parse) کریں۔
1 res.send(`2 <html>3 <body>4 <h2>Hello ${loginResponse.extract.nameID}</h2>5 </body>6 </html>7 `)8 res.send();ایک HTML رسپانس بھیجیں، صرف صارف کو یہ دکھانے کے لیے کہ ہمیں لاگ ان مل گیا ہے۔
1 } catch (err) {2 console.error('Error processing SAML response:', err);3 res.status(400).send('SAML authentication failed');4 }5 }6)ناکامی کی صورت میں صارف کو مطلع کریں۔
1spRouter.get('/login',جب براؤزر اس صفحے کو حاصل کرنے کی کوشش کرے تو لاگ ان کی درخواست بنائیں۔ یہ اوپر دی گئی ترتیب کے خاکہ میں مرحلہ 1 کا ہینڈلر ہے۔
1 async (req, res) => {2 const loginRequest = await sp.createLoginRequest(idp, "post")لاگ ان کی درخواست پوسٹ کرنے کے لیے معلومات حاصل کریں۔
1 res.send(`2 <html>3 <body>4 <script>5 window.onload = function () { document.forms[0].submit(); } 6 </script>یہ صفحہ فارم (نیچے دیکھیں) خود بخود جمع کر دیتا ہے۔ اس طرح صارف کو ری ڈائریکٹ ہونے کے لیے کچھ نہیں کرنا پڑتا۔ یہ اوپر دی گئی ترتیب کے خاکہ میں مرحلہ 2 ہے۔
1 <form method="post" action="${loginRequest.entityEndpoint}">loginRequest.entityEndpoint (شناختی فراہم کنندہ کے اینڈ پوائنٹ کا URL) پر پوسٹ کریں۔
1 <input type="hidden" name="${loginRequest.type}" value="${loginRequest.context}" />ان پٹ کا نام loginRequest.type (SAMLRequest) ہے۔ اس فیلڈ کا مواد loginRequest.context ہے، جو کہ دوبارہ XML ہے جو base64 انکوڈ شدہ ہے۔
1 </form>2 </body>3 </html>4 `) 5 }6)78app.use(express.urlencoded({extended: true}))یہ مڈل ویئر (opens in a new tab) HTTP درخواست (opens in a new tab) کی باڈی کو پڑھتا ہے۔ پہلے سے طے شدہ طور پر express اسے نظر انداز کر دیتا ہے، کیونکہ زیادہ تر درخواستوں کو اس کی ضرورت نہیں ہوتی۔ ہمیں اس کی ضرورت ہے کیونکہ POST باڈی کا استعمال کرتا ہے۔
1app.use(`/${config.spDir}`, spRouter)راؤٹر کو سروس فراہم کنندہ کی ڈائرکٹری (/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 on7 </button>8 </body>9 </html>10 `)11})سب دکھائیںاگر کوئی براؤزر روٹ ڈائرکٹری حاصل کرنے کی کوشش کرتا ہے، تو اسے لاگ ان صفحے کا لنک فراہم کریں۔
1app.listen(config.spPort, () => {2 console.log(`service provider is running on http://${config.spHostname}:${config.spPort}`)3})اس express ایپلیکیشن کے ساتھ spPort کو سنیں۔
src/idp.mts
یہ شناختی فراہم کنندہ ہے۔ یہ سروس فراہم کنندہ سے بہت ملتا جلتا ہے، ذیل میں دی گئی وضاحتیں ان حصوں کے لیے ہیں جو مختلف ہیں۔
1const xmlParser = new (await import("fast-xml-parser")).XMLParser(2 {3 ignoreAttributes: false, // خصوصیات کو برقرار رکھیں4 attributeNamePrefix: "@_", // خصوصیات کے لیے سابقہ5 }6)ہمیں سروس فراہم کنندہ سے موصول ہونے والی XML درخواست کو پڑھنے اور سمجھنے کی ضرورت ہے۔
1const getLoginPage = requestId => `یہ فنکشن آٹو سبمٹڈ فارم کے ساتھ وہ صفحہ بناتا ہے جو اوپر دی گئی ترتیب کے خاکہ کے مرحلہ 4 میں واپس کیا جاتا ہے۔
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 provider13 </button>سب دکھائیںدو فیلڈز ہیں جو ہم سروس فراہم کنندہ کو بھیجتے ہیں:
- وہ
requestIdجس کا ہم جواب دے رہے ہیں۔ - صارف کا شناخت کنندہ (ہم فی الحال وہ ای میل ایڈریس استعمال کرتے ہیں جو صارف فراہم کرتا ہے)۔
1 </form>2 </body>3</html>45const idpRouter = express.Router()67idpRouter.post("/loginSubmitted", async (req, res) => {8 const loginResponse = await idp.createLoginResponse(یہ اوپر دی گئی ترتیب کے خاکہ کے مرحلہ 5 کا ہینڈلر ہے۔ idp.createLoginResponse (opens in a new tab) لاگ ان کا رسپانس بناتا ہے۔
1 sp, 2 {3 authnContextClassRef: 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport',4 audience: sp.entityID,سامعین (audience) سروس فراہم کنندہ ہے۔
1 extract: {2 request: {3 id: req.body.requestId4 }5 },درخواست سے نکالی گئی معلومات۔ درخواست میں جس ایک پیرامیٹر کی ہمیں پرواہ ہے وہ requestId ہے، جو سروس فراہم کنندہ کو درخواستوں اور ان کے جوابات کو ملانے کی اجازت دیتا ہے۔
1 signingKey: { privateKey: idpPrivateKey, publicKey: config.idpCert } // دستخط کو یقینی بنائیںہمیں رسپانس پر دستخط کرنے کے لیے ڈیٹا رکھنے کے لیے signingKey کی ضرورت ہے۔ سروس فراہم کنندہ بغیر دستخط شدہ درخواستوں پر بھروسہ نہیں کرتا۔
1 },2 "post",3 {4 email: req.body.emailیہ صارف کی معلومات والا فیلڈ ہے جسے ہم واپس سروس فراہم کنندہ کو بھیجتے ہیں۔
1 }2 );34 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})سب دکھائیںدوبارہ، ایک آٹو سبمٹڈ فارم استعمال کریں۔ یہ اوپر دی گئی ترتیب کے خاکہ کا مرحلہ 6 ہے۔
12// لاگ ان کی درخواستوں کے لیے IdP اینڈ پوائنٹ3idpRouter.post(`/login`,یہ وہ اینڈ پوائنٹ ہے جو سروس فراہم کنندہ سے لاگ ان کی درخواست وصول کرتا ہے۔ یہ اوپر دی گئی ترتیب کے خاکہ کے مرحلہ 3 کا ہینڈلر ہے۔
1 async (req, res) => {2 try {3 // متبادل حل کیونکہ میں parseLoginRequest کو چلا نہیں سکا۔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"]))ہمیں تصدیقی درخواست کی ID پڑھنے کے لیے idp.parseLoginRequest (opens in a new tab) استعمال کرنے کے قابل ہونا چاہیے۔ تاہم، میں اسے کام نہیں کروا سکا اور اس پر زیادہ وقت صرف کرنا مناسب نہیں تھا اس لیے میں نے صرف ایک عام مقصد کا XML پارسر (opens in a new tab) استعمال کیا۔ ہمیں جس معلومات کی ضرورت ہے وہ <samlp:AuthnRequest> ٹیگ کے اندر ID وصف (attribute) ہے، جو XML کی اوپری سطح پر ہے۔
ایتھیریم سگنیچرز کا استعمال
اب جب کہ ہم سروس فراہم کنندہ کو صارف کی شناخت بھیج سکتے ہیں، اگلا قدم صارف کی شناخت کو قابل اعتماد طریقے سے حاصل کرنا ہے۔ Viem ہمیں صرف والیٹ سے صارف کا ایڈریس مانگنے کی اجازت دیتا ہے، لیکن اس کا مطلب براؤزر سے معلومات مانگنا ہے۔ ہم براؤزر کو کنٹرول نہیں کرتے، اس لیے ہم اس سے ملنے والے جواب پر خود بخود بھروسہ نہیں کر سکتے۔
اس کے بجائے، IdP براؤزر کو دستخط کرنے کے لیے ایک سٹرنگ بھیجنے والا ہے۔ اگر براؤزر میں موجود والیٹ اس سٹرنگ پر دستخط کرتا ہے، تو اس کا مطلب ہے کہ یہ واقعی وہی ایڈریس ہے (یعنی، یہ اس پرائیویٹ کلید کو جانتا ہے جو اس ایڈریس سے مطابقت رکھتی ہے)۔
اسے عملی طور پر دیکھنے کے لیے، موجودہ IdP اور SP کو روکیں اور یہ کمانڈز چلائیں:
1git checkout eth-signatures2pnpm install3pnpm startپھر SP پر (opens in a new tab) براؤز کریں اور ہدایات پر عمل کریں۔
نوٹ کریں کہ اس مقام پر ہم نہیں جانتے کہ ایتھیریم ایڈریس سے ای میل ایڈریس کیسے حاصل کیا جائے، اس لیے اس کے بجائے ہم SP کو <ethereum address>@bad.email.address رپورٹ کرتے ہیں۔
تفصیلی وضاحت
تبدیلیاں پچھلے خاکے کے مراحل 4-5 میں ہیں۔
ہم نے صرف idp.mts فائل کو تبدیل کیا ہے۔ یہاں تبدیل شدہ حصے ہیں۔
1import { v4 as uuidv4 } from 'uuid'2import { verifyMessage } from 'viem'ہمیں ان دو اضافی لائبریریوں کی ضرورت ہے۔ ہم نانس (nonce) (opens in a new tab) کی قدر بنانے کے لیے uuid (opens in a new tab) استعمال کرتے ہیں۔ قدر بذات خود اہمیت نہیں رکھتی، صرف یہ حقیقت اہم ہے کہ اسے صرف ایک بار استعمال کیا جاتا ہے۔
viem (opens in a new tab) لائبریری ہمیں ایتھیریم کی تعریفیں استعمال کرنے دیتی ہے۔ یہاں ہمیں اس کی ضرورت اس بات کی تصدیق کرنے کے لیے ہے کہ دستخط واقعی درست ہیں۔
1const loginPrompt = "To access the service provider, sign this nonce: "والیٹ صارف سے پیغام پر دستخط کرنے کی اجازت مانگتا ہے۔ ایک پیغام جو صرف ایک نانس ہو، صارفین کو الجھا سکتا ہے، اس لیے ہم یہ پرامپٹ شامل کرتے ہیں۔
1// requestIDs کو یہاں رکھیں2let nonces = {}ہمیں درخواست کی معلومات کی ضرورت ہے تاکہ ہم اس کا جواب دے سکیں۔ ہم اسے درخواست کے ساتھ بھیج سکتے ہیں (مرحلہ 4)، اور اسے واپس وصول کر سکتے ہیں (مرحلہ 5)۔ تاہم، ہم براؤزر سے ملنے والی معلومات پر بھروسہ نہیں کر سکتے، جو ممکنہ طور پر کسی مخالف صارف کے کنٹرول میں ہوتا ہے۔ اس لیے اسے یہاں نانس کو کلید کے طور پر استعمال کرتے ہوئے محفوظ کرنا بہتر ہے۔
نوٹ کریں کہ ہم سادگی کی خاطر اسے یہاں ایک متغیر (variable) کے طور پر کر رہے ہیں۔ تاہم، اس کے کئی نقصانات ہیں:
- ہم ڈینائل آف سروس (denial of service) حملے کا شکار ہو سکتے ہیں۔ ایک بدنیتی پر مبنی صارف کئی بار لاگ ان کرنے کی کوشش کر سکتا ہے، جس سے ہماری میموری بھر سکتی ہے۔
- اگر IdP کے عمل کو دوبارہ شروع کرنے کی ضرورت پڑتی ہے، تو ہم موجودہ اقدار کھو دیتے ہیں۔
- ہم متعدد عملوں (processes) میں لوڈ بیلنس نہیں کر سکتے، کیونکہ ہر ایک کا اپنا متغیر ہوگا۔
پروڈکشن سسٹم پر ہم ایک ڈیٹا بیس استعمال کریں گے اور کسی قسم کا ایکسپائری میکانزم نافذ کریں گے۔
1const getSignaturePage = requestId => {2 const nonce = uuidv4()3 nonces[nonce] = requestIdایک نانس بنائیں، اور مستقبل کے استعمال کے لیے requestId کو محفوظ کریں۔
1 return `2<html>3 <head>4 <script type="module">یہ جاوا اسکرپٹ صفحہ لوڈ ہونے پر خود بخود چل جاتی ہے۔
1 import { createWalletClient, custom, getAddress } from 'https://esm.sh/viem'ہمیں viem سے کئی فنکشنز کی ضرورت ہے۔
1 if (!window.ethereum) {2 alert("Please install MetaMask or a compatible wallet and then reload")3 }ہم صرف اسی صورت میں کام کر سکتے ہیں اگر براؤزر پر کوئی والیٹ موجود ہو۔
1 const [account] = await window.ethereum.request({method: 'eth_requestAccounts'})والیٹ (window.ethereum) سے اکاؤنٹس کی فہرست کی درخواست کریں۔ فرض کریں کہ کم از کم ایک موجود ہے، اور صرف پہلے والے کو محفوظ کریں۔
1 const walletClient = createWalletClient({2 account,3 transport: custom(window.ethereum)4 })براؤزر والیٹ کے ساتھ تعامل کرنے کے لیے ایک والیٹ کلائنٹ (opens in a new tab) بنائیں۔
1 window.goodSignature = () => {2 walletClient.signMessage({3 message: "${loginPrompt}${nonce}"صارف سے پیغام پر دستخط کرنے کو کہیں۔ چونکہ یہ پورا HTML ایک ٹیمپلیٹ سٹرنگ (opens in a new tab) میں ہے، اس لیے ہم idp کے عمل میں بیان کردہ متغیرات استعمال کر سکتے ہیں۔ یہ ترتیب کے خاکہ میں مرحلہ 4.5 ہے۔
1 }).then(signature => {2 const path= "/${config.idpDir}/signature/${nonce}/" + account + "/" + signature3 window.location.href = path4 })5 }/idp/signature/<nonce>/<address>/<signature> پر ری ڈائریکٹ کریں۔ یہ ترتیب کے خاکہ میں مرحلہ 5 ہے۔
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 = path6 }دستخط براؤزر کے ذریعے واپس بھیجے جاتے ہیں، جو ممکنہ طور پر بدنیتی پر مبنی ہو سکتا ہے (آپ کو براؤزر میں صرف http://localhost:3001/idp/signature/bad-nonce/bad-address/bad-signature کھولنے سے روکنے کے لیے کچھ نہیں ہے)۔ لہذا، یہ تصدیق کرنا ضروری ہے کہ IdP کا عمل خراب دستخطوں کو صحیح طریقے سے ہینڈل کرتا ہے۔
1 </script>2 </head>3 <body>4 <h2>Please sign</h2>5 <button onClick="window.goodSignature()">6 Submit a good (valid) signature7 </button>8 <br/>9 <button onClick="window.badSignature()">10 Submit a bad (invalid) signature11 </button>12 </body>13</html> 14`15}سب دکھائیںباقی صرف معیاری HTML ہے۔
1idpRouter.get("/signature/:nonce/:account/:signature", async (req, res) => {یہ ترتیب کے خاکہ میں مرحلہ 5 کا ہینڈلر ہے۔
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درخواست کی ID حاصل کریں، اور nonces سے نانس کو حذف کریں تاکہ یہ یقینی بنایا جا سکے کہ اسے دوبارہ استعمال نہیں کیا جا سکتا۔
1 try {چونکہ دستخط کے غلط ہونے کے بہت سے طریقے ہیں، اس لیے ہم اسے try ... catch بلاک میں لپیٹتے ہیں تاکہ کسی بھی پھینکی گئی خرابی (error) کو پکڑا جا سکے۔
1 const validSignature = await verifyMessage({2 address: req.params.account,3 message: `${loginPrompt}${req.params.nonce}`,4 signature: req.params.signature5 })ترتیب کے خاکہ میں مرحلہ 5.5 کو نافذ کرنے کے لیے verifyMessage (opens in a new tab) استعمال کریں۔
1 if (!validSignature)2 throw("Bad signature")3 } catch (err) {4 res.send("Error:" + err)5 return ;6 }باقی ہینڈلر اس کے مساوی ہے جو ہم نے پہلے /loginSubmitted ہینڈلر میں کیا تھا، سوائے ایک چھوٹی سی تبدیلی کے۔
1 const loginResponse = await idp.createLoginResponse(2 .3 .4 .5 {6 email: req.params.account + "@bad.email.address"7 }8 );ہمارے پاس اصل ای میل ایڈریس نہیں ہے (ہم اسے اگلے حصے میں حاصل کریں گے)، اس لیے فی الحال ہم ایتھیریم ایڈریس واپس کرتے ہیں اور اسے واضح طور پر نشان زد کرتے ہیں کہ یہ ای میل ایڈریس نہیں ہے۔
1// لاگ ان کی درخواستوں کے لیے IdP اینڈ پوائنٹ2idpRouter.post(`/login`,3 async (req, res) => {4 try {5 // متبادل حل کیونکہ میں parseLoginRequest کو چلا نہیں سکا۔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)سب دکھائیںgetLoginPage کے بجائے، اب مرحلہ 3 کے ہینڈلر میں getSignaturePage استعمال کریں۔
ای میل ایڈریس حاصل کرنا
اگلا قدم ای میل ایڈریس حاصل کرنا ہے، جو سروس فراہم کنندہ کی طرف سے درخواست کردہ شناخت کنندہ ہے۔ ایسا کرنے کے لیے، ہم ایتھیریم اٹیسٹیشن سروس (EAS) (opens in a new tab) استعمال کرتے ہیں۔
تصدیق نامے حاصل کرنے کا سب سے آسان طریقہ GraphQL API (opens in a new tab) کا استعمال کرنا ہے۔ ہم یہ کیوری (query) استعمال کرتے ہیں:
1query GetAttestationsByRecipient {2 attestations(3 where: { 4 recipient: { equals: "${getAddress(ethAddr)}" }5 schemaId: { equals: "0xfa2eff59a916e3cc3246f9aec5e0ca00874ae9d09e4678e5016006f07622f977" }6 }7 take: 18 ) { 9 data10 id11 attester12 }13}سب دکھائیںاس schemaId (opens in a new tab) میں صرف ایک ای میل ایڈریس شامل ہے۔ یہ کیوری اس اسکیما کے تصدیق ناموں کے لیے پوچھتی ہے۔ تصدیق کے موضوع کو recipient کہا جاتا ہے۔ یہ ہمیشہ ایک ایتھیریم ایڈریس ہوتا ہے۔
انتباہ: ہم یہاں جس طرح سے تصدیق نامے حاصل کر رہے ہیں اس میں دو حفاظتی مسائل ہیں۔
-
ہم API اینڈ پوائنٹ،
https://optimism.easscan.org/graphqlپر جا رہے ہیں، جو ایک مرکزی جزو ہے۔ ہمidوصف حاصل کر سکتے ہیں اور پھر آن چین (onchain) تلاش کر کے تصدیق کر سکتے ہیں کہ تصدیق نامہ اصلی ہے، لیکن API اینڈ پوائنٹ اب بھی ہمیں ان کے بارے میں نہ بتا کر تصدیق ناموں کو سنسر کر سکتا ہے۔یہ مسئلہ حل کرنا ناممکن نہیں ہے، ہم اپنا خود کا GraphQL اینڈ پوائنٹ چلا سکتے ہیں اور چین لاگز سے تصدیق نامے حاصل کر سکتے ہیں، لیکن یہ ہمارے مقاصد کے لیے ضرورت سے زیادہ ہے۔
-
ہم تصدیق کنندہ کی شناخت کو نہیں دیکھتے۔ کوئی بھی ہمیں غلط معلومات فراہم کر سکتا ہے۔ حقیقی دنیا کے نفاذ میں ہمارے پاس قابل اعتماد تصدیق کنندگان کا ایک سیٹ ہوگا اور ہم صرف ان کے تصدیق ناموں کو دیکھیں گے۔
اسے عملی طور پر دیکھنے کے لیے، موجودہ IdP اور SP کو روکیں اور یہ کمانڈز چلائیں:
1git checkout email-address2pnpm install3pnpm startپھر اپنا ای میل ایڈریس فراہم کریں۔ آپ کے پاس ایسا کرنے کے دو طریقے ہیں:
-
پرائیویٹ کلید کا استعمال کرتے ہوئے ایک والیٹ امپورٹ کریں، اور ٹیسٹنگ پرائیویٹ کلید
0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80استعمال کریں۔ -
اپنے ای میل ایڈریس کے لیے ایک تصدیق نامہ شامل کریں:
-
اٹیسٹیشن ایکسپلورر میں اسکیما (opens in a new tab) پر براؤز کریں۔
-
Attest with Schema پر کلک کریں۔
-
وصول کنندہ کے طور پر اپنا ایتھیریم ایڈریس، ای میل ایڈریس کے طور پر اپنا ای میل ایڈریس درج کریں، اور Onchain کو منتخب کریں۔ پھر Make Attestation پر کلک کریں۔
-
اپنے والیٹ میں ٹرانزیکشن کو منظور کریں۔ گیس کی ادائیگی کے لیے آپ کو آپٹیمزم بلاک چین (Optimism Blockchain) (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 کے تبدیل شدہ حصے ہیں۔
1import { GraphQLClient } from 'graphql-request'2import { SchemaEncoder } from '@ethereum-attestation-service/eas-sdk'ہمیں درکار لائبریریاں امپورٹ کریں۔
1const graphqlEndpointUrl = "https://optimism.easscan.org/graphql"ہر بلاک چین کے لیے ایک الگ اینڈ پوائنٹ (opens in a new tab) موجود ہے۔
1const graphqlClient = new GraphQLClient(graphqlEndpointUrl, { fetch })ایک نیا GraphQLClient کلائنٹ بنائیں جسے ہم اینڈ پوائنٹ سے کیوری کرنے کے لیے استعمال کر سکیں۔
1const graphqlSchema = 'string emailAddress'2const graphqlEncoder = new SchemaEncoder(graphqlSchema)GraphQL ہمیں صرف بائٹس کے ساتھ ایک غیر شفاف (opaque) ڈیٹا آبجیکٹ دیتا ہے۔ اسے سمجھنے کے لیے ہمیں اسکیما کی ضرورت ہے۔
1const ethereumAddressToEmail = async ethAddr => {ایتھیریم ایڈریس سے ای میل ایڈریس حاصل کرنے کا ایک فنکشن۔
1 const query = `2 query GetAttestationsByRecipient {یہ ایک GraphQL کیوری ہے۔
1 attestations(ہم تصدیق نامے تلاش کر رہے ہیں۔
1 where: { 2 recipient: { equals: "${getAddress(ethAddr)}" }3 schemaId: { equals: "0xfa2eff59a916e3cc3246f9aec5e0ca00874ae9d09e4678e5016006f07622f977" }4 }ہمیں جو تصدیق نامے درکار ہیں وہ ہمارے اسکیما میں ہیں، جہاں وصول کنندہ getAddress(ethAddr) ہے۔ getAddress (opens in a new tab) فنکشن اس بات کو یقینی بناتا ہے کہ ہمارے ایڈریس میں درست چیک سم (checksum) (opens in a new tab) موجود ہے۔ یہ ضروری ہے کیونکہ GraphQL کیس سینسیٹو (case-significant) ہے۔ "0xBAD060A7"، "0xBad060A7"، اور "0xbad060a7" مختلف اقدار ہیں۔
1 take: 1اس بات سے قطع نظر کہ ہمیں کتنے تصدیق نامے ملتے ہیں، ہمیں صرف پہلا والا چاہیے۔
1 ) {2 data3 id4 attester5 }6 }`وہ فیلڈز جو ہم وصول کرنا چاہتے ہیں۔
attester: وہ ایڈریس جس نے تصدیق نامہ جمع کرایا۔ عام طور پر یہ فیصلہ کرنے کے لیے استعمال ہوتا ہے کہ تصدیق نامے پر بھروسہ کیا جائے یا نہیں۔id: تصدیق نامے کی ID۔ آپ اس قدر کو آن چین تصدیق نامہ پڑھنے (opens in a new tab) کے لیے استعمال کر سکتے ہیں تاکہ یہ تصدیق کی جا سکے کہ GraphQL کیوری سے حاصل کردہ معلومات درست ہیں۔data: اسکیما کا ڈیٹا (اس صورت میں، ای میل ایڈریس)۔
1 const queryResult = await graphqlClient.request(query)23 if (queryResult.attestations.length == 0)4 return "no_address@available.is"اگر کوئی تصدیق نامہ نہیں ہے، تو ایک ایسی قدر واپس کریں جو واضح طور پر غلط ہو، لیکن جو سروس فراہم کنندہ کو درست معلوم ہو۔
1 const attestationDataFields = graphqlEncoder.decodeData(queryResult.attestations[0].data)2 return attestationDataFields[0].value.value3}اگر کوئی قدر موجود ہے، تو ڈیٹا کو ڈی کوڈ کرنے کے لیے decodeData استعمال کریں۔ ہمیں اس کے فراہم کردہ میٹا ڈیٹا کی ضرورت نہیں ہے، صرف قدر کی ضرورت ہے۔
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 );سب دکھائیںای میل ایڈریس حاصل کرنے کے لیے نیا فنکشن استعمال کریں۔
غیر مرکزیت (decentralization) کے بارے میں کیا خیال ہے؟
اس کنفیگریشن میں صارفین وہ ہونے کا دکھاوا نہیں کر سکتے جو وہ نہیں ہیں، جب تک کہ ہم ایتھیریم سے ای میل ایڈریس کی میپنگ کے لیے قابل اعتماد تصدیق کنندگان پر انحصار کرتے ہیں۔ تاہم، ہمارا شناختی فراہم کنندہ اب بھی ایک مرکزی جزو ہے۔ جس کے پاس بھی شناختی فراہم کنندہ کی پرائیویٹ کلید ہے وہ سروس فراہم کنندہ کو غلط معلومات بھیج سکتا ہے۔
ملٹی پارٹی کمپیوٹیشن (MPC) (opens in a new tab) کا استعمال کرتے ہوئے اس کا کوئی حل ہو سکتا ہے۔ مجھے امید ہے کہ میں مستقبل کے ٹیوٹوریل میں اس کے بارے میں لکھوں گا۔
نتیجہ
لاگ ان کے معیار، جیسے کہ ایتھیریم سگنیچرز، کو اپنانے میں مرغی اور انڈے کے مسئلے کا سامنا ہے۔ سروس فراہم کنندگان وسیع ترین ممکنہ مارکیٹ کو راغب کرنا چاہتے ہیں۔ صارفین چاہتے ہیں کہ وہ اپنے لاگ ان کے معیار کو سپورٹ کرنے کی فکر کیے بغیر سروسز تک رسائی حاصل کر سکیں۔ ایڈاپٹرز بنانا، جیسے کہ ایتھیریم IdP، ہمیں اس رکاوٹ پر قابو پانے میں مدد کر سکتا ہے۔
میرے مزید کام کے لیے یہاں دیکھیں (opens in a new tab)۔
صفحہ کی آخری اپ ڈیٹ: 3 مارچ، 2026





