پاس ورڈ صارفین اور سروس فراہم کرنے والوں دونوں کے لیے درد سر ہیں۔ صارفین کو اکثر پاس ورڈ یاد رکھنے میں دشواری ہوتی ہے، اور متعدد ویب سائٹس پر فشنگ اور پاس ورڈ کے دوبارہ استعمال کا زیادہ خطرہ ہوتا ہے۔
ڈویلپرز کو پاس ورڈ ہیش کرنا ہوں گے، ان پر نمک ڈالنا ہوگا، لاگ ان کی کوششوں کی شرح کو محدود کرنا ہوگا، اور "بھولے ہوئے پاس ورڈ” کا بہاؤ بنانا ہوگا جسے کوئی بھی استعمال کرنا پسند نہیں کرتا ہے۔
خوش قسمتی سے، کرپٹوگرافک کیز ان میں سے بہت سے مسائل کو حل کرتی ہیں۔ پاس ورڈ فراہم کرنے کے بجائے، آپ دیگر آلات کے ذریعے استعمال ہونے والے تصدیق کے مختلف طریقوں، جیسے فنگر پرنٹ، چہرے کی شناخت، اور PIN کے ذریعے اپنی شناخت کی تصدیق کر سکتے ہیں۔ توثیق کنندہ آلہ پر محفوظ کردہ ایک انکرپشن کلید کا استعمال کرتے ہوئے چیلنج پر دستخط کرتا ہے اور کبھی کسی اور کے ساتھ اشتراک نہیں کرتا ہے۔
یہ ٹیوٹوریل آپ کو دکھاتا ہے کہ ایسی ایپ کیسے بنائی جائے جو روایتی پاس ورڈ استعمال نہ کرے۔ بیک اینڈ ایک NestJS API ہے جو PostgreSQL میں صارفین اور ان کی خفیہ چابیاں محفوظ کرتا ہے۔ فرنٹ اینڈ ایک Next.js ایپ ہے جہاں صارفین سائن اپ کرتے ہیں اور صرف اپنے ای میل ایڈریس اور خفیہ کلید کے ساتھ لاگ ان ہوتے ہیں۔
SimpleWebAuthn، ایک اچھی طرح سے برقرار رکھنے والی TypeScript لائبریری کا استعمال کرتے ہوئے WebAuthn انکرپشن اور ڈیٹا پارسنگ سے متعلق کاموں کو آسان بنائیں۔
اس مضمون کے اختتام تک، آپ کے پاس وہ چیز ہو گی جو آپ کو اپنی ویب سائٹ کے لیے کرپٹوگرافک کلید کے اندراج اور توثیق کو لاگو کرنے اور اپنے سرور پر کرپٹوگرافک کیز کو محفوظ طریقے سے درست کرنے کے لیے درکار ہے۔ آپ اپنی خفیہ کلیدوں سے متعلق ممکنہ خطرات اور مسائل سے بچنے کے لیے بھی جان سکتے ہیں اور اقدامات کر سکتے ہیں جو آپ کے ایپس یا صارف اکاؤنٹس کو متاثر کر سکتے ہیں۔
آپ GitHub پر مکمل سورس کوڈ تلاش کر سکتے ہیں۔
انڈیکس
کوڈ کی ایک لائن لکھنے سے پہلے، آپ کو واضح طور پر اس بات کی وضاحت کرنے کی ضرورت ہے کہ آپ کیا تبدیل کرنا چاہتے ہیں۔
پاس ورڈ پر مبنی توثیق کئی دہائیوں سے لاگ ان کا ڈیفالٹ طریقہ رہا ہے۔ اور یہ کارآمد ثابت ہوا ہے اور معیار بن گیا ہے۔ لیکن جس ماڈل پر یہ انحصار کرتا ہے (ایک راز جسے انسان کو حفظ کرنا، داخل کرنا اور اپنے لیے رکھنا چاہیے۔) کی اہم حدود ہیں۔ جیسا کہ ہم دیکھیں گے، اس ماڈل کے ہر پہلو کے ساتھ مسائل ہیں۔
1. فشنگ حملہ
آپ کا پاس ورڈ جہاں بھی آپ اسے داخل کریں گے کام کرے گا۔ اگر کوئی حملہ آور آپ کے بینک کے لاگ ان صفحہ سے ملتا جلتا صفحہ بناتا ہے، تو پاس ورڈ اس صفحہ پر بھی وہی کام کرے گا۔ صرف ایک ہی چیز جو آپ کے صارفین کو سمجھوتہ کرنے سے بچائے گی اگر وہ دیکھتے ہیں کہ URL تھوڑا سا عجیب لگتا ہے۔
پوچھنے کو بہت کچھ ہے۔ فشنگ صفحات اکثر پکسل پرفیکٹ کے بہت قریب ہوتے ہیں اور فوری ای میلز یا ٹیکسٹ پیغامات کی شکل میں آتے ہیں تاکہ آپ کو فوری طور پر کارروائی کرنے کے لیے ڈیزائن کیا جائے۔ یہاں تک کہ سب سے زیادہ محتاط اور تکنیکی ذہن رکھنے والے صارفین کو بھی بے وقوف بنایا جا سکتا ہے۔
ایس ایم ایس یا توثیقی ایپ کے ذریعے بھیجے جانے والے ایک وقتی پاس ورڈ یقیناً مفید ہیں، لیکن یہ لوگوں کو جعلی صفحات پر اپنا پاس ورڈ داخل کرنے کے لیے بھی دھوکہ دے سکتے ہیں۔
2. پاس ورڈ دوبارہ استعمال کریں۔
زیادہ تر لوگوں کے پاس درجنوں، اگر سینکڑوں نہیں، تو آن لائن اکاؤنٹس ہیں۔ چونکہ آپ ہر ایک کے لیے ایک منفرد، مضبوط پاس ورڈ یاد نہیں رکھ سکتے، اس لیے آپ بورڈ میں ہر وقت وہی چند پاس ورڈ دوبارہ استعمال کرتے رہتے ہیں۔
حملہ آور یہ جانتے ہیں۔ اگر ایک سائٹ سے سمجھوتہ کیا جاتا ہے، تو یہ سمجھوتہ شدہ ای میل اور پاس ورڈ کے جوڑے لیتا ہے اور خود بخود انہیں دوسری سائٹوں پر آزماتا ہے۔ کہ اسناد بھرنا. ایک چھوٹے، طویل عرصے سے بھولے ہوئے فورم پر خلاف ورزی کسی کو آپ کے ای میل یا بینک اکاؤنٹ تک رسائی دے سکتی ہے۔
3. ڈیٹا کی خلاف ورزی
کوئی بھی سائٹ جو پاس ورڈز کا استعمال کرتی ہے اسے ایسی معلومات کو ذخیرہ کرنا چاہیے جو پاس ورڈز کی تصدیق کے لیے استعمال کی جا سکے۔ مثالی صورت حال یہ ہے کہ کسی سائٹ کو پاس ورڈ کے بجائے نمکین ہیش کو اسٹور کیا جائے۔ تاہم، تمام سائٹیں یہ اچھی طرح سے نہیں کرتی ہیں، اور جب حملہ آور کو ڈیٹا بیس کی کاپی مل جاتی ہے تو کمزور پاس ورڈز کی ہیش کو آف لائن ڈکرپٹ کیا جا سکتا ہے۔
یہ واضح طور پر ڈویلپرز کے لیے ایک بوجھ ہے۔ آپ کو صحیح ہیشنگ الگورتھم کا انتخاب کرنے، اسے ٹیون کرنے، اور اپنے ڈیٹا بیس کی حفاظت کرنے کی ضرورت ہے جبکہ اب بھی آپ کے صارفین کے لیے ایک آفت کے طور پر لیک ہونے کا علاج کرنا ہے۔
4. رازداری کی کمی
پاس ورڈ کے ساتھ، صارفین ہر اس سائٹ پر ایک مشترکہ راز منتقل کرتے ہیں جس کے ساتھ وہ سائن اپ کرتے ہیں۔ انہیں سائٹ کی حفاظت کے لیے اس پر بھروسہ کرنا ہوگا، اور ان کا اس پر کوئی کنٹرول نہیں ہے کہ اگر سائٹ کی خلاف ورزی کی جاتی ہے تو کیا ہوتا ہے۔
چونکہ لوگ ایک ہی ای میل اور پاس ورڈ کے امتزاج کو دوبارہ استعمال کرتے ہیں، اس لیے لیک شدہ اسناد کو خلاف ورزی میں ملایا جا سکتا ہے۔ نتیجے کے طور پر، تمام سروسز میں اکاؤنٹس کو جوڑنا اور افراد کے آن لائن رویے کے پروفائل بنانا بہت آسان ہو گیا ہے۔
5. سندی تھکاوٹ
پھر روزمرہ کی مایوسیاں ہیں۔ آپ پاس ورڈ کی پالیسیوں کو مسلسل جوڑ رہے ہیں (1 سے زیادہ علامت نہیں، 16 حروف سے زیادہ نہیں، وغیرہ)، ہر چند ماہ بعد ہارڈ ری سیٹ، سیکیورٹی سوالات، تصدیقی کوڈز وغیرہ۔ فہرست جاری ہے۔ آپ جو بھی نیا اکاؤنٹ بنائیں گے وہ اس میں شامل ہو جائے گا۔
یہ تھکاوٹ تیزی سے ایسے رویوں کی طرف لے جاتی ہے جو لوگوں کو اپنے پاس ورڈز کے بارے میں بہت زیادہ غیر محفوظ بناتے ہیں، جیسے کہ مختصر پاس ورڈ، ایک ہی پاس ورڈ کی چھوٹی تبدیلیاں، اور چپچپا نوٹوں پر لکھے گئے پاس ورڈ۔ کاروباری اخراجات بھی ہیں۔ لوگ سائن اپ کے عمل کے دوران چلے جاتے ہیں، اپنے اکاؤنٹ سے لاک آؤٹ ہو جاتے ہیں، اور پھر اپنا بھولا ہوا پاس ورڈ دوبارہ ترتیب دینے کے لیے سپورٹ سے رابطہ کریں۔
پاسکی مشترکہ راز کو مکمل طور پر ہٹا دیتی ہے۔ passkeys.dev اسے بدیہی، خود بخود منفرد، اور خلاف ورزی اور اینٹی فشنگ کے طور پر بیان کرتا ہے۔
ہر مسئلے کی تفصیل درج ذیل ہے:
| مسئلہ | پاسکی آپ کے مسئلے کو کیسے حل کرتی ہے۔ |
|---|---|
| فشنگ حملہ | براؤزرز اور آپریٹنگ سسٹم صرف اس سائٹ کی انکرپشن کیز استعمال کرتے ہیں جس نے انہیں بنایا ہے۔ جعلی ڈومینز، چاہے وہ کتنے ہی قائل کیوں نہ ہوں، آپ کو کہیں نہیں ملے گا۔ |
| پاس ورڈ کا دوبارہ استعمال | تمام انکرپشن کیز سائٹ کے لیے بنائے گئے نئے کلیدی جوڑے ہیں، اس لیے دوبارہ استعمال کرنے کے لیے کچھ نہیں ہے۔ |
| ڈیٹا کی خلاف ورزی | سرور صرف عوامی کلید کو ذخیرہ کرتا ہے۔ ایک لیک شدہ ڈیٹا بیس حملہ آوروں کو لاگ ان کرنے کے لیے کچھ بھی فراہم نہیں کرتا ہے۔ |
| رازداری کی کمی | سائٹ کے ساتھ اشتراک کردہ کوئی راز نہیں ہیں اور چابیاں سائٹ کے لیے مخصوص ہیں۔ فنگر پرنٹ اور چہرے کا ڈیٹا ڈیوائس کے باہر لیک نہیں ہوتا ہے۔ |
| سندی تھکاوٹ | بنانے یا یاد کرنے کے لیے کچھ نہیں ہے۔ صارفین وہی فنگر پرنٹ، چہرہ یا اسکرین لاک چیک کرتے ہیں جسے وہ دن میں کئی بار استعمال کرتے ہیں۔ |
بنیادی تبدیلی وہ ہے جہاں اعتماد ہوتا ہے۔ آپ کے پاس ورڈ کی حفاظت آپ کی دھوکہ دہی والی سائٹوں کو تلاش کرنے اور مضبوط اسناد منتخب کرنے کی صلاحیت پر منحصر ہے۔ خفیہ کلیدوں کے ساتھ، یہ براؤزر اور آپریٹنگ سسٹم ہے جو قوانین کو نافذ کرتا ہے، اور صارفین کو صرف اپنی شناخت کی تصدیق کرنے کی ضرورت ہوتی ہے۔
شرطیں
ساتھ چلنے کے لیے، آپ کو TypeScript سے واقف ہونا اور React کا تجربہ ہونا چاہیے۔ آپ کو NestJS کو اچھی طرح جاننے کی ضرورت نہیں ہے، لیکن اس سے یہ جاننے میں مدد ملتی ہے کہ کنٹرولرز اور خدمات کیا ہیں۔
آپ کے پاس درج ذیل ٹولز بھی انسٹال ہونے چاہئیں:
- Node.js ورژن 22 یا اس سے اوپر
- پوسٹگری ایس کیو ایل۔ macOS پر، Postgres.app سب سے آسان آپشن ہے۔ اگر آپ گرافیکل ٹولز کو ترجیح دیتے ہیں تو pgAdmin آسان ہے۔
- ایک ایسا آلہ جو کرپٹوگرافک کیز کو سپورٹ کرتا ہے۔ یہ تقریبا کسی بھی جدید لیپ ٹاپ یا فون کے ساتھ کام کرتا ہے، بشمول ٹچ آئی ڈی والا میک، ونڈوز ہیلو والا ونڈوز پی سی، اور اینڈرائیڈ فون یا آئی فون۔
پاسکی کیا ہے؟
ایک پاسکی عوامی کلید کی خفیہ نگاری پر مبنی ایک سند ہے۔ کلید بنانے سے آپ کے آلے پر کلیدی جوڑا بنتا ہے۔ آپ کی نجی کلیدیں آپ کے آلے پر آپ کے فنگر پرنٹ، چہرے، یا اسکرین لاک کے ساتھ مقفل رہتی ہیں۔ آپ کی عوامی کلید ویب سائٹ پر جائے گی اور آپ کے اکاؤنٹ کے آگے محفوظ ہو جائے گی۔
پاسکی کو Web Authentication API (WebAuthn) پر بنایا گیا ہے، یہ ایک W3C معیار ہے جسے تمام بڑے براؤزرز نے سپورٹ کیا ہے۔ "پاسکی” کی اصطلاح WebAuthn اسناد کے لیے صنعت کے لیے موزوں نام ہے جسے تمام آلات پر ہم آہنگ کیا جا سکتا ہے۔ اگر آپ مزید گہرائی سے تعارف چاہتے ہیں تو passkeys.dev ایک بہترین ذریعہ ہے۔
ایک تفصیل اکثر ڈویلپرز کو حیران کر دیتی ہے۔ ایپ آپ کو فنگر پرنٹ، چہرے کی شناخت، یا پن کوڈ کے درمیان انتخاب کرنے کی اجازت نہیں دیتی ہے۔ یہ آپریٹنگ سسٹم ہے جو آلہ کی صلاحیتوں اور آپ کی ترتیب کردہ ترتیبات کی بنیاد پر فیصلہ کرتا ہے۔
یہاں وہ ہے جو صارفین عام طور پر دیکھتے ہیں:
| آلہ | چیک کرنے کا طریقہ |
|---|---|
| سکاٹس مین | ID یا میک پاس ورڈ کو ٹچ کریں۔ |
| آئی فون یا آئی پیڈ | فیس آئی ڈی، ٹچ آئی ڈی، یا پاس کوڈ |
| اینڈروئیڈ | فنگر پرنٹ، چہرے کی شناخت کا انلاک، پن، پیٹرن، پاس ورڈ |
| کھڑکیاں | ونڈوز ہیلو چہرہ، فنگر پرنٹ، یا پن |
| ایک اور کمپیوٹر | QR کوڈ اسکین کریں اور اسے اپنے فون پر چیک کریں۔ |
سرور یہ دعویٰ کر سکتا ہے کہ کچھ مقامی چیکنگ ہوئی ہے۔ آپ کو ایک لمحے میں پتہ چل جائے گا کہ کیسے۔
کرپٹوکی توثیق کیسے کام کرتی ہے۔
سائن اپ کرنا اور لاگ ان کرنا دونوں ایک ہی تین قدمی پیٹرن کی پیروی کرتے ہیں۔
- سرور ایک بے ترتیب چیلنج تیار کرتا ہے اور اسے کچھ اختیارات کے ساتھ براؤزر کو بھیجتا ہے۔
- براؤزر ڈیوائس سے صارف کی تصدیق کرنے اور چیلنج پر دستخط کرنے کو کہتا ہے۔
- براؤزر ایک دستخط شدہ جواب واپس بھیجتا ہے اور سرور اس کی تصدیق کرتا ہے۔
رجسٹریشن کے دوران (WebAuthn اسے "اندراج” کہتا ہے)، ڈیوائس ایک نیا کلید جوڑا بناتا ہے اور ایک عوامی کلید واپس کرتا ہے۔ سرور پھر جواب کی توثیق کرتا ہے اور عوامی کلید کو ذخیرہ کرتا ہے۔
لاگ ان (تصدیق) کے دوران، آلہ ایک نئے سوال پر دستخط کرنے کے لیے موجودہ نجی کلید کا استعمال کرتا ہے۔ سرور پھر پہلے سے ذخیرہ شدہ عوامی کلید کا استعمال کرتے ہوئے دستخط کی توثیق کرتا ہے۔
پورے کوڈ میں دو اور تصورات ظاہر ہوتے ہیں۔
کہ انحصار کرنے والی پارٹی ID (RP ID) وہ ڈومین ہے جس سے پاسکی کا تعلق ہے۔ example.com. ترقی کے دوران، localhost. براؤزر دوسرے ڈومینز سے آپ کی خفیہ چابیاں استعمال کرنے سے انکار کرتے ہیں، جس سے آپ کی خفیہ کلیدیں فشنگ کے خلاف مزاحم بن جاتی ہیں۔
کہ صارف کی تصدیق (UV) پرچم دستخط شدہ جواب کے اندر تھوڑا سا ہے. یہ صرف ایک ڈیوائس پر ترتیب دیا جاتا ہے جب صارف واقعی فنگر پرنٹ، چہرے، یا اسکرین لاک کی توثیق مکمل کرتا ہے۔ سرور اس جھنڈے کو یہ یقینی بنانے کے لیے دیکھتا ہے کہ یہ کوئی بے ترتیب صارف کلید دبانے یا بٹن پر کلک کرنے والا نہیں تھا۔
منصوبے کی ساخت
پروجیکٹ میں ایک ذخیرہ میں دو الگ الگ ایپس ہیں۔
passwordless-auth/
├── client/ # Next.js frontend, runs on port 3000
└── server/ # NestJS API, runs on port 4000
چونکہ فرنٹ اینڈ اینڈ پوائنٹس پر انحصار کرتا ہے، ہم پہلے ایک سرور بناتے ہیں۔
NestJS بیک اینڈ کو کیسے ترتیب دیا جائے۔
Nest CLI کا استعمال کرتے ہوئے ایک نیا NestJS پروجیکٹ بنا کر شروع کریں۔
npx @nestjs/cli new server
cd server
پھر WebAuthn، ڈیٹا بیس، کنفیگریشن، اور JSON ویب ٹوکن (JWT) کے لیے انحصار انسٹال کریں۔
npm install @simplewebauthn/server @nestjs/typeorm typeorm pg @nestjs/config @nestjs/jwt @nestjs/passport passport passport-jwt
ڈیٹا بیس بنائیں
نامی ایک خالی ڈیٹا بیس بنائیں auth. ٹرمینل میں درج ذیل کو چلائیں:
createdb -h localhost -U postgres auth
اگر آپ pgAdmin کو ترجیح دیتے ہیں تو اس پر دائیں کلک کریں۔ ڈیٹا بیسمنتخب کریں بناناپھر ڈیٹا بیساسے نام دیں۔ auth.
اگر آپ اس مرحلہ کو چھوڑ دیتے ہیں، تو NestJS شروع ہونے پر ناکام ہو جائے گا۔ error: database "auth" does not exist. TypeORM ٹیبل بنا سکتا ہے، لیکن یہ خود ڈیٹا بیس نہیں بناتا۔
ماحولیاتی متغیر شامل کریں۔
بنانا .env فائل server فولڈر:
JWT_SECRET=replace-with-a-long-random-string
WEBAUTHN_RP_ID=localhost
WEBAUTHN_ORIGIN=http://localhost:3000
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=your-database-password
DB_NAME=auth
WEBAUTHN_RP_ID صرف ایک ڈومین ہے، کوئی پروٹوکول یا بندرگاہیں نہیں ہیں۔ WEBAUTHN_ORIGIN یہ فرنٹ اینڈ کا عین مطابق URL ہے۔ سرور دوسرے ذرائع سے آنے والے تمام جوابات کو مسترد کرتا ہے۔
براؤزرز صرف HTTPS سائٹوں سے یا یہاں سے WebAuthn کی اجازت دیتے ہیں: localhostلہذا یہ ترتیب بغیر کسی سند کے ترقی کے دوران کام کرتی ہے۔
PostgreSQL سے جڑیں۔
بنانا src/auth/config/database.config.ts گروپ ڈیٹا بیس کی ترتیبات کے لیے:
import { registerAs } from '@nestjs/config';
export default registerAs('database', () => ({
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
synchronize: process.env.NODE_ENV !== 'production',
}));
جب تم ہو config فولڈر، شامل کریں src/auth/config/jwt.config.ts ٹوکن کی ترتیبات کے لیے:
import { registerAs } from '@nestjs/config';
export default registerAs('jwt', () => ({
secret: process.env.JWT_SECRET,
expiresIn: '1h',
}));
کہ synchronize اختیارات TypeORM کو اداروں کی بنیاد پر ٹیبل بنانے اور اپ ڈیٹ کرنے کی ہدایت کرتے ہیں۔ یہ ترقی کے لیے آسان ہے، لیکن پروڈکشن میں جانے سے پہلے آپ کو ہجرت پر سوئچ کرنے کی ضرورت ہوگی۔
پھر سب کچھ جوڑیں۔ src/app.module.ts:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from './auth/auth.module';
import databaseConfig from './auth/config/database.config';
import jwtConfig from './auth/config/jwt.config';
import { User, Authenticator } from './auth/entities/auth.entity';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [databaseConfig, jwtConfig],
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('database.host'),
port: configService.get('database.port'),
username: configService.get('database.username'),
password: configService.get('database.password'),
database: configService.get('database.database'),
entities: [User, Authenticator],
synchronize: configService.get('database.synchronize'),
}),
inject: [ConfigService],
}),
AuthModule,
],
})
export class AppModule {}
آخر میں، CORS کو فعال کریں: src/main.ts لہذا پورٹ 3000 پر ایک فرنٹ اینڈ پورٹ 4000 پر API کو کال کرسکتا ہے۔
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
app.enableCors({
origin: true,
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization'],
});
await app.listen(4000);
console.log('App running on port 4000');
}
bootstrap();
origin: true کسی بھی ذریعہ سے درخواستیں قبول کرکے مقامی ترقی کو آسان بنایا جاتا ہے۔ تعینات کرنے سے پہلے اسے اپنے فرنٹ اینڈ کے URL سے بدل دیں۔
ڈیٹا بیس اداروں کی وضاحت کیسے کریں۔
اس کے لیے دو میزیں درکار ہیں۔ یوزر ٹیبل اکاؤنٹس پر مشتمل ہے اور تصدیق کنندہ ٹیبل میں خفیہ کلیدیں ہیں۔ ایک صارف کے پاس متعدد پاس کیز ہو سکتی ہیں (مثال کے طور پر، ایک ان کے لیپ ٹاپ پر اور ایک ان کے فون پر)۔
بنانا src/auth/entities/auth.entity.ts:
import {
Entity,
Column,
PrimaryGeneratedColumn,
ManyToOne,
OneToMany,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import type {
CredentialDeviceType,
Base64URLString,
} from '@simplewebauthn/server';
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
email: string;
@Column({ unique: true })
webauthnId: Base64URLString;
@Column({ type: 'varchar', nullable: true })
currentChallenge?: string | null;
@OneToMany(() => Authenticator, (authenticator) => authenticator.user)
authenticators: Authenticator[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
@Entity()
export class Authenticator {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
credentialId: Base64URLString;
@Column({ type: 'bytea' })
credentialPublicKey: Uint8Array;
@ManyToOne(() => User, (user) => user.authenticators, { onDelete: 'CASCADE' })
user: User;
@Column()
webauthnUserId: Base64URLString;
@Column({ type: 'bigint' })
counter: number;
@Column({
type: 'varchar',
length: 32,
})
deviceType: CredentialDeviceType;
@Column()
backedUp: boolean;
@Column('simple-array', { nullable: true })
transports?: string[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
آئیے ان شعبوں میں سے کچھ کو قریب سے دیکھتے ہیں۔
سب سے پہلے webauthnId ایک صارف کا ہینڈل جو آلہ پر خفیہ کلید کے ساتھ محفوظ ہوتا ہے۔ WebAuthn کی تفصیلات بتاتی ہیں کہ اس میں ذاتی معلومات نہیں ہونی چاہئیں، اس لیے اسے ای میلز کے بجائے بے ترتیب بائٹس سے بھرنا چاہیے۔
اگلا، currentChallenge سرور کے پاس ایک چیلنج آپ کا منتظر ہے۔ آپشن بناتے وقت سیٹ کریں اور کامیاب تصدیق کے بعد کلیئر ہو جائیں۔ توجہ فرمائیں string | null زمرہ یہ اہم ہے اور آپ سیکیورٹی سیکشن میں دیکھ سکتے ہیں۔
counter دستخطی کاؤنٹر۔ کچھ مستند ہر استعمال کے ساتھ اس میں اضافہ کرتے ہیں، سرور کو کلون شدہ آلات کا پتہ لگانے کی اجازت دیتے ہیں۔ مطابقت پذیر خفیہ کلیدیں عام طور پر کاؤنٹر کو 0 پر رکھتی ہیں، اور SimpleWebAuthn دونوں منظرناموں کی حمایت کرتا ہے۔
deviceType اور backedUp یہ آپ کو بتاتا ہے کہ آیا آپ کی انکرپشن کیز کو تمام آلات پر ہم آہنگ کیا جا سکتا ہے اور کیا ان کا فی الحال بیک اپ لیا گیا ہے (جیسے iCloud کیچین میں)۔
اور آخر میں، transports ریکارڈ کرتا ہے کہ براؤزر تصدیق کنندہ تک کیسے رسائی حاصل کرسکتا ہے (آلہ میں بنایا گیا، USB، بلوٹوتھ یا فون کے ذریعے)۔
سرور پر ایک انکرپشن کلید کو کیسے رجسٹر کریں۔
بنانا src/auth/auth.service.ts. اپنی کلاس ترتیب دے کر شروع کریں۔
import {
Injectable,
UnauthorizedException,
ConflictException,
Logger,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { randomBytes } from 'crypto';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User, Authenticator } from './entities/auth.entity';
import {
generateRegistrationOptions,
generateAuthenticationOptions,
verifyRegistrationResponse,
verifyAuthenticationResponse,
} from '@simplewebauthn/server';
import type {
AuthenticationResponseJSON,
PublicKeyCredentialCreationOptionsJSON,
PublicKeyCredentialRequestOptionsJSON,
RegistrationResponseJSON,
} from '@simplewebauthn/server';
@Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
private readonly rpName="Passwordless Auth";
private readonly rpID: string;
private readonly origin: string;
constructor(
@InjectRepository(User)
private readonly userRepository: Repository,
@InjectRepository(Authenticator)
private readonly authenticatorRepository: Repository,
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
) {
this.rpID = this.configService.get('WEBAUTHN_RP_ID');
this.origin = this.configService.get('WEBAUTHN_ORIGIN');
}
rpName ایک انسانی پڑھنے کے قابل ایپ کا نام۔ صارفین اس معلومات کو آپریٹنگ سسٹم کے پاس ورڈ کی پرامپٹ اور بعد میں پاس ورڈ مینیجر میں دیکھ سکتے ہیں۔
رجسٹریشن کے اختیارات بنائیں
پہلا طریقہ صارف کو تلاش کرتا ہے یا تخلیق کرتا ہے اور پھر SimpleWebAuthn سے رجسٹریشن کے آپشن کے لیے پوچھتا ہے۔ اسے اپنی کلاس میں شامل کریں:
async generateRegistrationOptions(
email: string,
): Promise {
let user = await this.userRepository.findOne({ where: { email } });
if (!user) {
user = this.userRepository.create({
email,
webauthnId: randomBytes(32).toString('base64url'),
});
await this.userRepository.save(user);
}
const authenticators = await this.authenticatorRepository.find({
where: { user: { id: user.id } },
});
if (authenticators.length > 0) {
throw new ConflictException(
'An account with this email already exists. Please sign in.',
);
}
const options = await generateRegistrationOptions({
rpName: this.rpName,
rpID: this.rpID,
userID: Buffer.from(user.webauthnId, 'base64url'),
userName: user.email,
attestationType: 'none',
authenticatorSelection: {
residentKey: 'required',
userVerification: 'required',
},
excludeCredentials: authenticators.map((authenticator) => ({
id: authenticator.credentialId,
transports: authenticator.transports,
})),
});
// Save the current challenge
user.currentChallenge = options.challenge;
await this.userRepository.save(user);
return options;
}
کہ authenticatorSelection بلاک وہ جگہ ہے جہاں آپ فیصلہ کرتے ہیں کہ آپ کس قسم کی پاسکی چاہتے ہیں۔
residentKey: 'required' قابل بازیافت اسناد کی درخواست کریں۔ یہی چیز اسے ایک حقیقی خفیہ کلید بناتی ہے ( اسناد ڈیوائس پر یا صارف کے پاس ورڈ مینیجر میں محفوظ کی جاتی ہیں اور پھر دوسرے آلات کے ساتھ مطابقت پذیر ہو سکتی ہیں)۔
userVerification: 'required' آلہ کو مطلع کرتا ہے کہ اسے صارف کی تصدیق کرنے کی ضرورت ہے (یعنی فنگر پرنٹ، چہرہ، یا اسکرین لاک کی توثیق کریں)۔ بصورت دیگر، کچھ تصدیق کنندگان صرف وجود کی جانچ کریں گے اور اصل میں صارف کی تصدیق نہیں کریں گے۔
جو غائب ہے وہ ہے مستند کنکشن کا اختیار۔ اگر مقرر کیا گیا ہے۔ 'platform'یہ صارف کو اپنے موجودہ ڈیوائس پر سینسر استعمال کرنے میں مؤثر طریقے سے لاک کر دیتا ہے۔ اگر آپ اسے چھوڑ دیتے ہیں، تو آپ اپنے فون کو QR کوڈ یا ہارڈویئر سیکیورٹی کلید کے ذریعے استعمال کر سکتے ہیں (passkeys.dev کی طرف سے تجویز کردہ)۔
attestationType: 'none' اس کا مطلب ہے کہ ہم آپ کے آلے کو اس کا میک اور ماڈل ثابت کرنے کے لیے نہیں کہیں گے۔ زیادہ تر صارف ایپس کو اس معلومات کی بالکل بھی ضرورت نہیں ہوتی ہے، اور اس کی درخواست کرنے کا نتیجہ درحقیقت اضافی رازداری کے پیغامات کا باعث بن سکتا ہے۔
excludeCredentials چونکہ یہ صارف کے پاس پہلے سے موجود خفیہ کلیدوں کی فہرست بناتا ہے، اس لیے آلہ پہلے سے اندراج شدہ تصدیق کنندگان کے لیے ڈپلیکیٹس نہیں بناتا۔ اگر آپ اوپر کے چیک کو دیکھیں تو اس معاملے میں فہرست ہمیشہ خالی رہتی ہے۔ اگر آپ بعد میں لاگ ان ہونے والے صارفین کو خفیہ کلیدیں شامل کرنے کی اجازت دینا چاہتے ہیں تو یہ مفید ہے۔
اپنے موجودہ مستند کو چیک کرنا بھی ضروری ہے۔ چونکہ یہ اختتامی نقطہ صارفین کو لاگ ان کرنے کی ضرورت نہیں رکھتا ہے، اس لیے یہ صارفین کو کسی ایسے اکاؤنٹ میں خفیہ کلید شامل کرنے کی اجازت نہیں دے سکتا جس کے پاس پہلے سے موجود ہے۔ بصورت دیگر، جو بھی آپ کا ای میل پتہ جانتا ہے وہ آپ کے اکاؤنٹ سے اپنی خفیہ کلید منسلک کر سکتا ہے۔
رجسٹریشن کا جواب چیک کریں۔
جب صارف ڈیوائس پر تصدیق کرتا ہے، تو براؤزر جواب بھیجتا ہے۔ دوسرا طریقہ اس کی تصدیق کرتا ہے اور نئی خفیہ کلید کو محفوظ کرتا ہے۔
async verifyRegistration(email: string, response: RegistrationResponseJSON) {
const user = await this.userRepository.findOne({
where: { email },
select: ['id', 'email', 'currentChallenge', 'webauthnId'],
});
if (!user) {
throw new UnauthorizedException('User not found');
}
const verification = await verifyRegistrationResponse({
response,
expectedChallenge: user.currentChallenge,
expectedOrigin: this.origin,
expectedRPID: this.rpID,
requireUserVerification: true,
});
const { verified, registrationInfo } = verification;
if (verified && registrationInfo) {
const { credential, credentialDeviceType, credentialBackedUp } =
registrationInfo;
const newAuthenticator = this.authenticatorRepository.create({
credentialId: credential.id,
credentialPublicKey: credential.publicKey,
counter: credential.counter,
user: user,
webauthnUserId: user.webauthnId,
deviceType: credentialDeviceType,
backedUp: credentialBackedUp,
transports: response.response.transports,
});
await this.authenticatorRepository.save(newAuthenticator);
user.currentChallenge = null;
await this.userRepository.save(user);
return {
verified,
user: { id: user.id, email: user.email },
};
}
throw new UnauthorizedException('Registration verification failed');
}
verifyRegistrationResponse یہ آپ کے کوڈ میں سخت محنت کر رہا ہے۔ تصدیق کریں کہ چیلنج آپ کے محفوظ کردہ سے میل کھاتا ہے، کہ جواب ماخذ اور RP IDs سے آیا ہے، اور یہ کہ دستخط درست ہے۔ کے ساتھ requireUserVerification: true اگر UV پرچم سیٹ نہیں کیا گیا ہے تو یہ جواب دینے سے بھی انکار کرتا ہے۔ یہ جھنڈا اس بات کا ثبوت ہے کہ فنگر پرنٹ، چہرے، یا اسکرین لاک کی توثیق ہو گئی ہے۔
اگر سب کچھ ٹھیک ہو جاتا ہے تو، کریڈینشل ID اور عوامی کلید کو محفوظ کریں، پھر چیلنج کو صاف کریں تاکہ انہیں دوبارہ استعمال نہ کیا جا سکے۔
سرور پر خفیہ کلید کا استعمال کرتے ہوئے لاگ ان کیسے کریں۔
لاگ ان اسی طرح کام کرتا ہے۔ یعنی آپ ایک آپشن تیار کریں اور پھر جواب چیک کریں۔
تصدیق کے اختیارات بنائیں
async generateAuthenticationOptions(
email: string,
): Promise {
const user = await this.userRepository.findOne({ where: { email } });
if (!user) {
throw new UnauthorizedException('User not found');
}
const authenticators = await this.authenticatorRepository.find({
where: { user: { id: user.id } },
});
if (authenticators.length === 0) {
throw new UnauthorizedException(
'No passkey registered for this account. Please sign up.',
);
}
const options = await generateAuthenticationOptions({
rpID: this.rpID,
allowCredentials: authenticators.map((authenticator) => ({
id: authenticator.credentialId,
transports: authenticator.transports,
})),
userVerification: 'required',
});
// Save the current challenge
user.currentChallenge = options.challenge;
await this.userRepository.save(user);
return options;
}
allowCredentials اس صارف کی ملکیتی خفیہ کلیدوں کی فہرست دیتا ہے۔ براؤزر اس فہرست کو آلہ پر صحیح فہرست تلاش کرنے کے لیے استعمال کرتا ہے۔ transports اشارے آپ کو یہ فیصلہ کرنے میں مدد کریں گے کہ آیا اسے مقامی طور پر دیکھنا ہے، اپنی سیکیورٹی کلید میں، یا آپ کے فون پر۔
تصدیق کا جواب چیک کریں۔
async verifyAuthentication(
email: string,
response: AuthenticationResponseJSON,
) {
const user = await this.userRepository.findOne({
where: { email },
select: ['id', 'email', 'currentChallenge'],
});
if (!user) {
throw new UnauthorizedException('User not found');
}
const authenticator = await this.authenticatorRepository.findOne({
where: { credentialId: response.id, user: { id: user.id } },
});
if (!authenticator) {
throw new UnauthorizedException('Authenticator not found');
}
const verification = await verifyAuthenticationResponse({
response,
expectedChallenge: user.currentChallenge,
expectedOrigin: this.origin,
expectedRPID: this.rpID,
credential: {
id: authenticator.credentialId,
publicKey: new Uint8Array(authenticator.credentialPublicKey),
counter: Number(authenticator.counter),
transports: authenticator.transports,
},
requireUserVerification: true,
});
const { verified } = verification;
if (verified) {
authenticator.counter = verification.authenticationInfo.newCounter;
authenticator.backedUp =
verification.authenticationInfo.credentialBackedUp;
await this.authenticatorRepository.save(authenticator);
user.currentChallenge = null;
await this.userRepository.save(user);
// Generate JWT token
const payload = { sub: user.id, email: user.email };
return {
accessToken: this.jwtService.sign(payload),
user: { id: user.id, email: user.email },
};
}
throw new UnauthorizedException('Authentication verification failed');
}
استفسار اسناد کی ID اور صارف ID دونوں کا استعمال کرتا ہے۔ براہ کرم اپنی صارف کی شرائط کو حذف نہ کریں۔ اس کی وجوہات سیکورٹی سیکشن میں بیان کی جائیں گی۔
کہ Number() کیونکہ کاؤنٹر کے آس پاس فون ہیں۔ pg ڈرائیور واپس bigint کالم ٹو سٹرنگ۔ SimpleWebAuthn کو ایک نمبر درکار ہے۔
دستخط کی تصدیق ہونے کے بعد، ہم کاؤنٹر کو بڑھاتے ہیں، چیلنج کو صاف کرتے ہیں، اور JWT جاری کرتے ہیں۔ اس وقت سے، ایپ آپ کے ساتھ وہی سلوک کرے گی جو آپ کے پاس ورڈ کے ساتھ لاگ ان کرنے والے کسی بھی صارف کے ساتھ کرتی ہے۔ فرق صرف اتنا ہے کہ کوئی پاس ورڈ استعمال نہیں کیا جاتا۔
توثیق کے اختتامی نقطہ کو کیسے بے نقاب کریں۔
بنانا src/auth/auth.controller.ts. اس کے پاس سائن اپ اور لاگ ان کے بہاؤ میں ہر قدم کے لیے ایک راستہ ہے، اور ایک محفوظ راستہ جو صارف کی خفیہ کلید کو درج کرتا ہے۔
import { Controller, Post, Body, Get, Query, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('register/options')
async getRegistrationOptions(
@Body() body: { email: string },
): Promise {
return this.authService.generateRegistrationOptions(body.email);
}
@Post('register/verify')
async verifyRegistration(
@Body() body: { email: string; response: any },
) {
return this.authService.verifyRegistration(body.email, body.response);
}
@Post('login/options')
async getAuthenticationOptions(
@Body() body: { email: string },
): Promise {
return this.authService.generateAuthenticationOptions(body.email);
}
@Post('login/verify')
async verifyAuthentication(
@Body() body: { email: string; response: any },
) {
return this.authService.verifyAuthentication(body.email, body.response);
}
@Get('authenticators')
@UseGuards(JwtAuthGuard)
async getAuthenticators(@Query('email') email: string) {
return this.authService.getAuthenticators(email);
}
}
کہ response جسم مندرجہ ذیل طور پر داخل کیا جاتا ہے: any اس کی وجہ یہ ہے کہ خدمت کے طریقہ کار نے پہلے ہی درست SimpleWebAuthn قسم کا اعلان کر دیا ہے جس کی اسے توقع ہے۔
JWT کے ساتھ راستے کی حفاظت کیسے کریں۔
آخری راستہ استعمال کرتا ہے:JwtAuthGuardلہذا، صرف لاگ ان صارفین ہی کال کر سکتے ہیں۔ اس کی حمایت چھوٹے سروس انتظامات سے ہوتی ہے۔ اسے اگلی بار شامل کریں۔ AuthService:
async getAuthenticators(email: string) {
const user = await this.userRepository.findOne({ where: { email } });
if (!user) {
throw new UnauthorizedException('User not found');
}
const authenticators = await this.authenticatorRepository.find({
where: { user: { id: user.id } },
});
return authenticators;
}
اگلا، ہم پاسپورٹ کی حکمت عملی بناتے ہیں جو ٹوکن کو پڑھتا اور اس کی تصدیق کرتا ہے۔ src/auth/strategies/jwt.strategy.ts:
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET'),
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email };
}
}
پھر ایک گارڈ بنائیں۔ src/auth/guards/jwt-auth.guard.ts. یہ پاسپورٹ کے بلٹ ان گارڈز کو لپیٹتا ہے اور ایک واضح غلطی کا پیغام دیتا ہے۔
import {
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor() {
super();
}
handleRequest(err: any, user: any, info: any) {
if (info instanceof TokenExpiredError) {
throw new UnauthorizedException('Token has expired');
}
if (info instanceof JsonWebTokenError) {
throw new UnauthorizedException('Invalid token');
}
if (err || !user) {
throw new UnauthorizedException('Authentication failed');
}
return user;
}
// Optional: Override canActivate if you need custom logic before JWT validation
async canActivate(context: ExecutionContext): Promise {
const request = context.switchToHttp().getRequest();
const authHeader = request.headers.authorization;
if (!authHeader) {
throw new UnauthorizedException('No token provided');
}
const [type] = authHeader.split(' ');
if (type !== 'Bearer') {
throw new UnauthorizedException('Invalid token format');
}
return super.canActivate(context) as Promise;
}
}
تصدیقی ماڈیول کو کیسے رجسٹر کریں۔
آخر میں، سب کچھ ایک ساتھ رکھو. src/auth/auth.module.ts:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { User, Authenticator } from './entities/auth.entity';
import { JwtStrategy } from './strategies/jwt.strategy';
@Module({
imports: [
TypeOrmModule.forFeature([User, Authenticator]),
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('JWT_SECRET'),
signOptions: {
expiresIn: '1d',
},
}),
inject: [ConfigService],
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
کہ signOptions یہ وہ جگہ ہے جہاں آپ سروس کے مسائل کو ٹوکن کنٹرول کرتے ہیں، لہذا ہر لاگ ان ایک دن کے لیے درست ہے۔
سرور شروع کریں:
npm run start:dev
TypeORM ہے۔ user اور authenticator آپ کی میز auth ڈیٹا بیس آپ pgAdmin کھول کر ریفریش کر سکتے ہیں۔ میز نوڈ دیکھیں۔
Next.js فرنٹ اینڈ کو کیسے ترتیب دیا جائے۔
TypeScript، Tailwind CSS، App Router، اور src فولڈر:
npx create-next-app@latest client --typescript --tailwind --app --src-dir
cd client
npm install @simplewebauthn/browser axios
اضافہ .env فائل client API کی طرف اشارہ کرنے والا فولڈر:
NEXT_PUBLIC_API_URL=http://localhost:4000
پھر ہم ایک چھوٹا API کلائنٹ بناتے ہیں۔ client/src/services/api.ts:
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios from 'axios';
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
'Content-Type': 'application/json',
},
});
export const webAuthnApi = {
getRegistrationOptions: async (email: string) => {
const response = await api.post('/auth/register/options', { email });
return response.data;
},
verifyRegistration: async (email: string, response: any) => {
const res = await api.post('/auth/register/verify', { email, response });
return res.data;
},
getAuthenticationOptions: async (email: string) => {
const response = await api.post('/auth/login/options', { email });
return response.data;
},
verifyAuthentication: async (email: string, response: any) => {
const res = await api.post('/auth/login/verify', { email, response });
return res.data;
},
};
// Add request interceptor to include JWT token
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
export default api;
صارف کے لاگ ان ہونے کے بعد نیچے والا انٹرسیپٹر ہر درخواست کے ساتھ JWT منسلک کرتا ہے۔ /auth/authenticators راستہ متوقع ہے۔
سائن اپ پیج کیسے بنایا جائے۔
WebAuthn کا فرنٹ اینڈ حصہ حیرت انگیز طور پر مختصر ہے۔ SimpleWebAuthn startRegistration فنکشن سرور سے اختیارات حاصل کرتا ہے، براؤزر کے WebAuthn API کو کال کرتا ہے، اور JSON فارمیٹ میں جواب دیتا ہے جسے سرور سمجھ سکتا ہے۔
بنانا client/src/app/signup/page.tsx:
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client';
import React, { useState } from 'react';
import { startRegistration, browserSupportsWebAuthn } from '@simplewebauthn/browser';
import { webAuthnApi } from '@/services/api';
import { getPasskeyErrorMessage } from '@/services/passkey';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
export default function SignupPage() {
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const router = useRouter();
const handleRegistration = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
if (!browserSupportsWebAuthn()) {
setError('This browser does not support passkeys.');
setLoading(false);
return;
}
try {
const options = await webAuthnApi.getRegistrationOptions(email);
const regResult = await startRegistration({ optionsJSON: options });
const verificationResult = await webAuthnApi.verifyRegistration(email, regResult);
if (verificationResult.verified) {
router.push('/welcome');
}
} catch (err: any) {
console.error('Registration error:', err);
setError(getPasskeyErrorMessage(err, 'Registration failed. Please try again.'));
} finally {
setLoading(false);
}
};
return (
Already have an account?{' '}
Sign in
);
}
کہ handleRegistration فنکشن پچھلے تین مراحل کی پیروی کرتا ہے۔ سرور سے اختیارات حاصل کرتا ہے اور انہیں سرور تک پہنچاتا ہے۔startRegistrationتصدیق کے لیے نتائج واپس بھیجیں۔
کال کریں startRegistration یہ وہ جگہ ہے جہاں جادو ہوتا ہے۔ براؤزر آپریٹنگ سسٹم کا پاس کی ڈائیلاگ کھولتا ہے اور صارف Touch ID، Face ID، Windows Hello، یا اپنے فون سے تصدیق کرتا ہے۔ آپ کا کوڈ وعدہ کے حل ہونے کا انتظار کر رہا ہے۔
براہ کرم اس کا حوالہ دیں۔ { optionsJSON: options } تنازعہ SimpleWebAuthn کے پرانے ورژن نے آپشن کو براہ راست قبول کیا، اور بہت سے ٹیوٹوریلز اب بھی اسے اسی طرح پیش کرتے ہیں۔ ورژن 13 اب بھی پرانے طرز کی حمایت کرتا ہے، لیکن ایک انتباہ لاگ ان کرتا ہے، لہذا آبجیکٹ کی قسم استعمال کریں۔
لاگ ان پیج کیسے بنایا جائے۔
کہ Sign in صفحہ ہے۔ Sign up صفحہ بنانا client/src/app/signin/page.tsxسائن اپ صفحہ کو کاپی کریں اور نیچے کی طرف اشارہ کرنے کے لیے عنوان اور لنک کو تبدیل کریں: /signup. پھر درآمد کریں۔ startAuthentication اس کے بجائے startRegistrationجمع کرانے والے ہینڈلر کو درج ذیل ہینڈلر سے بدلیں:
const handleAuthentication = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
if (!browserSupportsWebAuthn()) {
setError('This browser does not support passkeys.');
setLoading(false);
return;
}
try {
const options = await webAuthnApi.getAuthenticationOptions(email);
const authResult = await startAuthentication({ optionsJSON: options });
const verificationResult = await webAuthnApi.verifyAuthentication(email, authResult);
if (verificationResult.accessToken) {
localStorage.setItem('token', verificationResult.accessToken);
router.push('/welcome');
}
} catch (err: any) {
console.error('Authentication error:', err);
setError(getPasskeyErrorMessage(err, 'Authentication failed. Please try again.'));
} finally {
setLoading(false);
}
};
پاس کرنا یاد رکھیں handleAuthentication فارم پر onSubmit. آپ ہمارے GitHub ذخیرہ میں مکمل صفحہ دیکھ سکتے ہیں۔
اگر تصدیق کامیاب ہو جاتی ہے، تو سرور JWT واپس کرتا ہے اور صفحہ JWT کو ری ڈائریکٹ کرنے سے پہلے اسٹور کرتا ہے۔ ٹوکن اسٹوریج localStorage آئیے اس ڈیمو کو سادہ رکھیں۔ پروڈکشن ایپس میں httpOnly سرور کی طرف سے سیٹ کی گئی کوکیز ایک محفوظ انتخاب ہیں کیونکہ وہ صفحہ پر چلنے والے JavaScript کے ذریعے نہیں پڑھ سکتے ہیں۔

کارآمد ایرر میسیجز کو کیسے ڈسپلے کریں۔
اگر انکرپشن کلیدی مشق کے دوران کوئی مسئلہ پیش آجائے تو براؤزر DOMException اور name یہ آپ کو بتاتا ہے کہ کیا ہوا ہے۔ خام پیغامات بہت دوستانہ نہیں ہوتے ہیں، لہذا عام پیغامات کا ترجمہ کرنا اچھا خیال ہے۔
بنانا client/src/services/passkey.ts:
/* eslint-disable @typescript-eslint/no-explicit-any */
// Turn errors from the browser's WebAuthn API and our backend into readable messages
export function getPasskeyErrorMessage(err: any, fallback: string): string {
if (err?.response?.data?.message) {
return err.response.data.message;
}
switch (err?.name) {
case 'NotAllowedError':
// User cancelled the fingerprint/face/screen-lock prompt, or it timed out
return 'The passkey prompt was cancelled or timed out. Please try again.';
case 'InvalidStateError':
// A passkey for this account already exists on this authenticator
return 'This device already has a passkey for this account. Please sign in instead.';
case 'NotSupportedError':
return 'This device does not support passkeys.';
case 'SecurityError':
return 'Passkeys are not allowed on this domain. Check the WebAuthn RP ID and origin settings.';
default:
return fallback;
}
}
NotAllowedError یہ وہی ہے جو آپ سب سے زیادہ دیکھیں گے۔ فائر اس وقت ہوتا ہے جب صارف پرامپٹ بند کرتا ہے، بہت زیادہ انتظار کرتا ہے، یا بائیو میٹرک چیک میں ناکام ہوجاتا ہے۔ SecurityError اس کا عام طور پر مطلب یہ ہوتا ہے کہ RP ID اس ڈومین سے مماثل نہیں ہے جس پر صفحہ چل رہا ہے۔ یہ کنفیگریشن کا مسئلہ ہے، صارف کا مسئلہ نہیں۔
اپنی ایپ کی جانچ کیسے کریں۔
دونوں ایپس کو الگ الگ ٹرمینلز میں شروع کریں۔
# Terminal 1
cd server
npm run start:dev
# Terminal 2
cd client
npm run dev
تحریک http://localhost:3000/signupاپنا ای میل درج کریں اور بٹن پر کلک کریں۔ آپ کا آلہ آپ کو پاس کی کے لیے اشارہ کرے گا۔ Touch ID کو سپورٹ کرنے والے Macs پر، آپ کو سینسر کو چھونے کے لیے کہا جائے گا۔ بصورت دیگر، آپ کو اپنے MacBook صارف پاس ورڈ کے ساتھ فارم پُر کرنے کی ضرورت ہوگی۔ Windows میں، Windows Hello آپ کے چہرے، فنگر پرنٹ، یا PIN کے بارے میں پوچھتا ہے۔ اگر آپ کوئی دوسرا آلہ استعمال کرنے کا انتخاب کرتے ہیں، تو آپ کا براؤزر ایک QR کوڈ دکھائے گا جسے آپ اپنے فون سے اسکین کر سکتے ہیں۔
تصدیق ہونے کے بعد، ایک خوش آمدید صفحہ ظاہر ہو گا۔ اب جائیں: /signinبراہ کرم وہی ای میل درج کریں اور دوبارہ چیک کریں۔ اس بار سرور JWT واپس کرتا ہے۔
اگر آپ کے پاس پاس کلید سے چلنے والا آلہ دستیاب نہیں ہے تو، کروم کے پاس بلٹ ان ورچوئل آتھنٹیکیٹر ہے۔ DevTools کھولیں، پھر تھری ڈاٹ مینو پر کلک کریں۔ مزید اوزار اور منتخب کریں ویب کی توثیق. ورچوئل مستند ماحول کو فعال کریں اور مستند استعمال کرتے ہوئے شامل کریں: "صارف کی توثیق کی حمایت کرتا ہے” تصدیق شدہ۔ اس کے بعد کروم ایک خفیہ کلیدی ڈیوائس کی تقلید کرے گا۔
آپ pgAdmin بھی کھول سکتے ہیں اور دیکھ سکتے ہیں: authenticator میز آپ کو کریڈینشل آئی ڈی، بائٹس کے طور پر محفوظ کردہ عوامی کلید اور ڈیوائس کی قسم نظر آئے گی۔ کوئی پاس ورڈ نہیں ہے، کوئی راز نہیں ہے۔
حفاظتی غلطیوں سے بچنا ہے۔
اگرچہ پاس کیز ڈیزائن کے لحاظ سے محفوظ ہیں، لیکن ان کے ارد گرد کا سرور کوڈ اب بھی درست ہونا ضروری ہے۔ اس پروجیکٹ کی تعمیر کے دوران، میں نے ذیل میں سے ہر ایک کا سامنا کیا۔ ان میں سے کچھ خطرناک ہیں کیونکہ وہ غلطیاں نہیں پھینکتے ہیں۔
اسناد کی شناخت کو دو بار انکوڈ کریں۔
تازہ ترین ورژن میں سادہ ویب تصدیقیہاں استعمال شدہ ورژن 14 سمیت۔ credential.id یہ پہلے سے ہی ایک base64url سٹرنگ ہے۔ یہ میرا پہلا کوڈ ورژن ہے:
// Wrong: encodes an already-encoded string
credentialId: Buffer.from(credential.id).toString('base64url'),
Buffer.from چونکہ اسٹرنگ کو UTF-8 ٹیکسٹ کے طور پر سمجھا جا رہا ہے، اس کا نتیجہ base64url سٹرنگ کی ایک base64url انکوڈنگ ہے۔ رجسٹریشن کامیاب رہی، لیکن لاگ ان کرتے وقت آپ کی اسناد نہیں مل سکتیں کیونکہ براؤزر اصل ID بھیجتا ہے۔ اسٹور credential.id اس طرح
خام چیلنج کو صاف کریں۔ undefined
تمام چیلنجز کو صرف ایک بار استعمال کیا جانا چاہیے۔ ایسا لگتا ہے کہ یہ لائن اسے صاف کرتی ہے۔
user.currentChallenge = undefined;
تاہم، TypeORM کو چھوڑ دیا گیا ہے۔ undefined جب آپ کسی ہستی کو محفوظ کرتے ہیں، تو آپ اس کی خصوصیات کو محفوظ کرتے ہیں، اس لیے پچھلے چیلنجز ڈیٹا بیس میں ہی رہتے ہیں۔ کوئی بھی جو درست جواب حاصل کرتا ہے وہ اسے دوبارہ چلا سکتا ہے۔ قدر کو اس پر سیٹ کریں: null اس کے بجائے، چیک کریں کہ آیا آپ کے کالم کی قسم اس کی اجازت دیتی ہے۔
مالک کی تصدیق کیے بغیر اسناد کی تلاش
ہم صرف کریڈینشل ID کی بنیاد پر ایک مستند تلاش کرنے کی تجویز کرتے ہیں۔
// Wrong: any user's passkey matches
where: { credentialId: response.id }
لیکن مسئلہ یہ ہے: حملہ آور آپ کے ای میل کے لیے لاگ ان کے اختیارات کی درخواست کرتا ہے اور اپنی خفیہ کلید سے چیلنج پر دستخط کرتا ہے۔ پھر سرور حملہ آور کی خفیہ کلید کی تصدیق کرتا ہے، عوامی کلید کے خلاف دستخط کی تصدیق کرتا ہے، اور اکاؤنٹ کے لیے ایک ٹوکن جاری کرتا ہے۔ پچھلی مثال کے بعد، ہم ہمیشہ صارف کو استفسار میں شامل کرتے ہیں۔
سائن اپ کرتے وقت موجودہ اکاؤنٹس میں خفیہ کلیدیں شامل کرنے کی اجازت دیں۔
رجسٹریشن کا اختتامی نقطہ عوامی ہے۔ اگر آپ کسی ایسے اکاؤنٹ میں خفیہ کلید شامل کرنا چاہتے ہیں جو آپ کے ای میل سے مماثل ہو، تو کوئی بھی جو آپ کے ای میل کو جانتا ہے وہ اپنی خفیہ کلید کو رجسٹر کر سکتا ہے اور آپ کے طور پر لاگ ان کر سکتا ہے۔
اگر آپ کے اکاؤنٹ میں پہلے سے ہی پاس کی ہے تو رجسٹریشن کی اجازت نہ دیں۔ اگر آپ لوگوں کو مزید کلیدیں شامل کرنے کی اجازت دینا چاہتے ہیں، تو آپ ایک علیحدہ خصوصیت استعمال کرسکتے ہیں جس کے لیے لاگ ان کی ضرورت ہوتی ہے۔
تصدیق کے لیے پوچھے بغیر تصدیق کی درخواست کریں۔
درخواست پر userVerification: 'preferred'آپ کا آلہ فنگر پرنٹ یا پن کی توثیق کو چھوڑ سکتا ہے۔ پھر اگر آپ چیک کریں۔ requireUserVerification: trueSimpleWebAuthn کے ڈیفالٹ کے نتیجے میں ان آلات کو استعمال کرنے والے صارفین کے لیے ایک مبہم غلطی ہو گی۔ منتخب کریں 'required' اختیارات اور تصدیق کے میچ کو یقینی بنانے کے لیے دونوں طرف چیک کریں۔
میں یہاں سے کہاں جاؤں؟
ہم نے جو ایپ بنائی ہے وہ بنیادی کرپٹوگرافک کلید کے بہاؤ کا احاطہ کرتی ہے۔ لیکن اگر آپ مزید جانا چاہتے ہیں تو آپ کچھ بہتری لا سکتے ہیں۔
سیکرٹ کلید خودکار تکمیل (جسے مشروط UI بھی کہا جاتا ہے) براؤزر کو محفوظ کردہ خفیہ کلیدوں کو براہ راست ای میل فیلڈ میں تجویز کرنے کی اجازت دیتا ہے، جیسا کہ یہ محفوظ کردہ پاس ورڈ تجویز کرتا ہے۔ SimpleWebAuthn اس کی حمایت کرتا ہے: useBrowserAutofill اختیارات startAuthentication.
صارفین دوسرے آلات کے لیے پاس کیز بھی شامل کرنا چاہیں گے اور پرانی پاس کیز کو ہٹانا چاہیں گے۔ ہم اسے JWT گارڈ کے پیچھے اینڈ پوائنٹس کے ایک جوڑے کے طور پر بناتے ہیں۔
آخر میں، چیلنج ختم ہونا ضروری ہے. ایسا کرنے کا ایک آسان طریقہ یہ ہے کہ اس کے ساتھ ٹائم اسٹیمپ اسٹور کریں۔ currentChallenge اس کے بعد یہ چند منٹ سے زیادہ پرانی کسی بھی چیز کو مسترد کر دیتا ہے۔ بڑی ایپس ممکنہ طور پر چیلنجز کو صارف کی سرگزشت کے بجائے سیشن یا کیشے (جیسے Redis) میں اسٹور کریں گی۔
نتیجہ
اس ٹیوٹوریل میں، ہم نے خفیہ کلیدوں کا استعمال کرتے ہوئے مکمل طور پر پاس ورڈ کے بغیر تصدیق کا نظام بنایا ہے۔ ایک مکمل NestJS API ہے جو آپ کو WebAuthn چیلنجز بنانے، SimpleWebAuthn کا استعمال کرتے ہوئے دستخط شدہ جوابات کی تصدیق کرنے اور PostgreSQL میں پبلک کیز کو اسٹور کرنے کی اجازت دیتا ہے۔ اس میں ایک Next.js فرنٹ اینڈ بھی ہے جو صارفین کو اپنے آلے پر فنگر پرنٹ، چہرہ، یا اسکرین لاک استعمال کرکے سائن اپ کرنے اور لاگ ان کرنے کی اجازت دیتا ہے۔
اب آپ جانتے ہیں کہ توثیق اور دستخط کا بہاؤ کیسے کام کرتا ہے، صارف کی تصدیق کے جھنڈے کیوں اہم ہیں، اور سرور کیوں یہ تعین نہیں کر سکتا کہ صارف کو کون سا بائیو میٹرک طریقہ پیش کیا جائے۔ ہم نے کچھ ایسے باریک بگز کی بھی نشاندہی کی ہے جو لاگ ان میں ناکامی یا اکاؤنٹ ٹیک اوور کا سبب بن سکتے ہیں، اور ان سے کیسے بچنا ہے۔
پاس ورڈ کسی بھی وقت جلد ختم نہیں ہو رہے ہیں۔ تاہم، پاسکی پہلے سے ہی آپ کی ملکیت کے تقریباً ہر ڈیوائس پر تعاون یافتہ ہے۔ اب آپ جانتے ہیں کہ اسے اپنی ایپ میں کیسے شامل کرنا ہے۔
مکمل سورس کوڈ GitHub پر دستیاب ہے۔ اسے نقل کرتے رہیں، اسے الگ کرتے رہیں، اور اسے اپنا بنائیں۔ اگر آپ کو یہ ٹیوٹوریل کسی بھی طرح سے کارآمد لگتا ہے، تو براہ کرم اسے کسی ایسے شخص کو بھیج دیں جس سے جلد ہی یہی سوال ہو سکتا ہے۔