كيفية إجراء مصادقة JWT باستخدام Angular 6 SPA

نشرت: 2022-03-11

اليوم سنلقي نظرة على مدى سهولة دمج مصادقة رمز الويب JSON (JWT) في تطبيق Angular 6 (أو الأحدث) أحادي الصفحة (SPA). لنبدأ بقليل من الخلفية.

ما هي رموز ويب JSON ولماذا نستخدمها؟

الإجابة الأسهل والأكثر إيجازًا هنا هي أنها مريحة ومضغوطة وآمنة. لنلقِ نظرة على تلك الادعاءات بالتفصيل:

  1. ملائم : يتطلب استخدام JWT للمصادقة إلى النهاية الخلفية بمجرد تسجيل الدخول تعيين رأس HTTP واحد ، وهي مهمة يمكن أتمتتها بسهولة من خلال وظيفة أو تصنيف فرعي ، كما سنرى لاحقًا.
  2. مضغوط : الرمز المميز هو ببساطة سلسلة مشفرة بـ base64 ، تحتوي على عدد قليل من حقول الرأس ، وحمولة إذا لزم الأمر. عادةً ما يكون إجمالي JWT أقل من 200 بايت ، حتى إذا تم التوقيع عليه.
  3. آمن : على الرغم من أنه ليس مطلوبًا ، فإن ميزة أمان رائعة لـ JWT هي أنه يمكن توقيع الرموز المميزة باستخدام تشفير زوج المفاتيح العام / الخاص RSA أو تشفير HMAC باستخدام سر مشترك. هذا يضمن أصل وصلاحية الرمز المميز.

ما يتلخص في كل هذا هو أن لديك طريقة آمنة وفعالة لمصادقة المستخدمين ، ثم التحقق من المكالمات إلى نقاط نهاية API الخاصة بك دون الحاجة إلى تحليل أي هياكل بيانات أو تنفيذ التشفير الخاص بك.

نظرية التطبيق

تدفق البيانات النموذجي لمصادقة JWT والاستخدام بين أنظمة الواجهة الأمامية والخلفية

لذلك ، مع القليل من الخلفية ، يمكننا الآن الغوص في كيفية عمل هذا في تطبيق فعلي. في هذا المثال ، سأفترض أن لدينا خادم Node.js يستضيف واجهة برمجة التطبيقات الخاصة بنا ، ونعمل على تطوير قائمة مهام SPA باستخدام Angular 6. لنعمل أيضًا مع بنية API هذه:

  • /authPOST (نشر اسم المستخدم وكلمة المرور لمصادقة واستعادة JWT)
  • /todosGET (إرجاع قائمة بعناصر قائمة المهام للمستخدم)
  • /todos/{id}GET (إرجاع عنصر قائمة مهام محدد)
  • /usersGET (إرجاع قائمة المستخدمين)

سننتقل إلى إنشاء هذا التطبيق البسيط قريبًا ، ولكن في الوقت الحالي ، دعونا نركز على التفاعل من الناحية النظرية. لدينا صفحة تسجيل دخول بسيطة ، حيث يمكن للمستخدم إدخال اسم المستخدم وكلمة المرور. عندما يتم إرسال النموذج ، فإنه يرسل تلك المعلومات إلى نقطة نهاية /auth . يمكن لخادم Node بعد ذلك المصادقة على المستخدم بأي طريقة مناسبة (البحث في قاعدة البيانات ، والاستعلام عن خدمة ويب أخرى ، وما إلى ذلك) ولكن في النهاية تحتاج نقطة النهاية إلى إرجاع JWT.

سوف تحتوي JWT لهذا المثال على عدد قليل من المطالبات المحجوزة ، وبعض المطالبات الخاصة . المطالبات المحجوزة هي ببساطة أزواج قيمة مفتاح موصى بها من قِبل JWT تُستخدم عادةً للمصادقة ، في حين أن المطالبات الخاصة هي أزواج ذات قيمة مفتاح تنطبق فقط على تطبيقنا:

المطالبات المحجوزة

  • iss : مُصدر هذا الرمز المميز. عادةً ما يكون FQDN الخاص بالخادم ، ولكن يمكن أن يكون أي شيء طالما يعلم تطبيق العميل أنه يتوقع ذلك.
  • exp : تاريخ انتهاء الصلاحية ووقت هذا الرمز المميز. هذا في ثوانٍ منذ منتصف ليل 1 يناير 1970 بتوقيت غرينتش (بتوقيت يونكس).
  • nbf : غير صالح قبل الطابع الزمني. لا تُستخدم كثيرًا ، ولكنها تعطي حدًا أدنى لنافذة الصلاحية. نفس تنسيق exp .

المطالبات الخاصة

  • uid : معرف المستخدم الخاص بالمستخدم الذي قام بتسجيل الدخول.
  • role : الدور المعين للمستخدم الذي قام بتسجيل الدخول.

سيتم ترميز معلوماتنا باستخدام base64 وتوقيعها باستخدام HMAC مع المفتاح المشترك todo-app-super-shared-secret . فيما يلي مثال لما تبدو عليه JWT:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b2RvYXBpIiwibmJmIjoxNDk4MTE3NjQyLCJleHAiOjE0OTgxMjEyNDIsInVpZCI6MSwicm9sZSI6ImFkbWluIn0.ZDz_1vcIlnZz64nSM28yA1s-4c_iw3Z2ZtP-SgcYRPQ

هذه السلسلة هي كل ما نحتاجه للتأكد من أن لدينا معلومات تسجيل دخول صالحة ، ومعرفة أي مستخدم متصل ، ومعرفة حتى الدور (الأدوار) الذي يقوم به المستخدم.

تشرع معظم المكتبات والتطبيقات في تخزين JWT هذا في localStorage أو sessionStorage لسهولة الاسترجاع ، ولكن هذه مجرد ممارسة شائعة. ما تفعله بالرمز متروك لك ، طالما يمكنك توفيره لمكالمات API المستقبلية.

الآن ، كلما أراد SPA إجراء مكالمة إلى أي من نقاط نهاية API المحمية ، فإنه يحتاج ببساطة إلى الإرسال على طول الرمز المميز في رأس Authorization HTTP.

 Authorization: Bearer {JWT Token}

ملاحظة : مرة أخرى ، هذه ببساطة ممارسة شائعة. لا تنص JWT على أي طريقة معينة لإرسال نفسها إلى الخادم. يمكنك أيضًا إلحاقه بعنوان URL ، أو إرساله في ملف تعريف ارتباط.

بمجرد أن يتلقى الخادم JWT ، يمكنه فك تشفيره ، والتأكد من التناسق باستخدام السر المشترك لـ HMAC ، والتحقق من انتهاء الصلاحية باستخدام الحقلين exp و nbf . iss أيضًا استخدام حقل الإصدار للتأكد من أنه جهة الإصدار الأصلية لهذه JWT.

بمجرد اقتناع الخادم بصلاحية الرمز المميز ، يمكن استخدام المعلومات المخزنة داخل JWT. على سبيل المثال ، يمنحنا uid الذي قمنا بتضمينه معرف المستخدم الذي قدم الطلب. بالنسبة لهذا المثال المحدد ، قمنا أيضًا بتضمين حقل role ، والذي يتيح لنا اتخاذ قرارات حول ما إذا كان يجب أن يكون المستخدم قادرًا على الوصول إلى نقطة نهاية معينة أم لا. (يعتمد ما إذا كنت تثق في هذه المعلومات ، أو تريد إجراء بحث في قاعدة البيانات على مستوى الأمان المطلوب.)

 function getTodos(jwtString) { var token = JWTDecode(jwtstring); if( Date.now() < token.nbf*1000) { throw new Error('Token not yet valid'); } if( Date.now() > token.exp*1000) { throw new Error('Token has expired'); } if( token.iss != 'todoapi') { throw new Error('Token not issued here'); } var userID = token.uid; var todos = loadUserTodosFromDB(userID); return JSON.stringify(todos); }

لنقم ببناء تطبيق Todo بسيط

للمتابعة ، ستحتاج إلى تثبيت إصدار حديث من Node.js (6.x أو أحدث) و npm (3.x أو أحدث) و angular-cli. إذا كنت بحاجة إلى تثبيت Node.js ، والذي يتضمن npm ، فيرجى اتباع التعليمات الواردة هنا. بعد ذلك يمكن تثبيت angular-cli cli باستخدام npm (أو yarn ، إذا قمت بتثبيته):

 # installation using npm npm install -g @angular/cli # installation using yarn yarn global add @angular/cli

لن أخوض في التفاصيل في Angular 6 boilerplate التي سنستخدمها هنا ، ولكن بالنسبة للخطوة التالية ، قمت بإنشاء مستودع Github لاحتواء تطبيق todo صغير لتوضيح بساطة إضافة مصادقة JWT إلى تطبيقك. ما عليك سوى استنساخها باستخدام ما يلي:

 git clone https://github.com/sschocke/angular-jwt-todo.git cd angular-jwt-todo git checkout pre-jwt

يتحول الأمر git checkout pre-jwt إلى إصدار مسمى حيث لم يتم تنفيذ JWT.

يجب أن يكون هناك مجلدان بالداخل يسمى server client . الخادم هو خادم Node API الذي سيستضيف واجهة برمجة التطبيقات الأساسية الخاصة بنا. العميل هو تطبيق Angular 6 الخاص بنا.

خادم Node API

للبدء ، قم بتثبيت التبعيات وابدأ خادم API.

 cd server # installation using npm npm install # or installation using yarn yarn node app.js

يجب أن تكون قادرًا على متابعة هذه الروابط والحصول على تمثيل JSON للبيانات. فقط في الوقت الحالي ، إلى أن نحصل على المصادقة ، قمنا بترميز نقطة النهاية /todos لإرجاع مهام userID=1 :

  • http: // localhost: 4000: اختبار الصفحة لمعرفة ما إذا كان خادم العقدة قيد التشغيل
  • http: // localhost: 4000 / api / users: إرجاع قائمة المستخدمين على النظام
  • http: // localhost: 4000 / api / todos: إرجاع قائمة مهام userID=1

التطبيق الزاوي

لبدء استخدام تطبيق العميل ، نحتاج أيضًا إلى تثبيت التبعيات وبدء خادم التطوير.

 cd client # using npm npm install npm start # using yarn yarn yarn start

ملاحظة : بناءً على سرعة الخط لديك ، قد يستغرق تنزيل جميع التبعيات بعض الوقت.

إذا كان كل شيء يسير على ما يرام ، يجب أن ترى الآن شيئًا كهذا عند الانتقال إلى http: // localhost: 4200:

الإصدار الذي لا يدعم JWT من تطبيق Angular Todo List الخاص بنا.

إضافة المصادقة عبر JWT

لإضافة دعم لمصادقة JWT ، سنستخدم بعض المكتبات القياسية المتاحة التي تجعلها أكثر بساطة. يمكنك بالطبع التخلي عن وسائل الراحة هذه وتنفيذ كل شيء بنفسك ، لكن هذا خارج نطاقنا هنا.

أولاً ، دعنا نثبت مكتبة على جانب العميل. تم تطويره وصيانته بواسطة Auth0 ، وهي مكتبة تتيح لك إضافة مصادقة مستندة إلى مجموعة النظراء إلى موقع ويب. لا يتطلب استخدام المكتبة نفسها استخدام خدماتها.

 cd client # installation using npm npm install @auth0/angular-jwt # installation using yarn yarn add @auth0/angular-jwt

سنصل إلى الكود في غضون ثانية ، ولكن أثناء وجودنا فيه ، فلنقم بإعداد جانب الخادم أيضًا. سنستخدم مكتبات body-parser و jsonwebtoken و express-jwt لجعل Node يفهم أجسام JSON POST و JWTs.

 cd server # installation using npm npm install body-parser jsonwebtoken express-jwt # installation using yarn yarn add body-parser jsonwebtoken express-jwt

نقطة نهاية API للمصادقة

أولاً ، نحتاج إلى طريقة لمصادقة المستخدمين قبل منحهم رمزًا مميزًا. بالنسبة إلى العرض التوضيحي البسيط الخاص بنا ، سنقوم فقط بإعداد نقطة نهاية مصادقة ثابتة باستخدام اسم مستخدم وكلمة مرور مشفرة. يمكن أن يكون هذا بسيطًا أو معقدًا كما يتطلب التطبيق الخاص بك. الشيء المهم هو إرسال JWT.

في server/app.js ، أضف إدخالاً أسفل سطور require الأخرى كما يلي:

 const bodyParser = require('body-parser'); const jwt = require('jsonwebtoken'); const expressJwt = require('express-jwt');

بالإضافة إلى ما يلي:

 app.use(bodyParser.json()); app.post('/api/auth', function(req, res) { const body = req.body; const user = USERS.find(user => user.username == body.username); if(!user || body.password != 'todo') return res.sendStatus(401); var token = jwt.sign({userID: user.id}, 'todo-app-super-shared-secret', {expiresIn: '2h'}); res.send({token}); });

هذا هو في الغالب رمز JavaScript أساسي. نحصل على نص JSON الذي تم تمريره إلى نقطة النهاية /auth ، والعثور على مستخدم يطابق اسم المستخدم هذا ، وتحقق من وجود مستخدم وكلمة المرور متطابقتين ، وإرجاع 401 Unauthorized إذا لم يكن كذلك.

الجزء المهم هو إنشاء الرمز المميز ، وسنقوم بتقسيمه وفقًا لمعاييره الثلاثة. بناء جملة sign كما يلي: jwt.sign(payload, secretOrPrivateKey, [options, callback]) ، حيث:

  • payload هي كائن حرفي لأزواج القيمة الرئيسية التي ترغب في تشفيرها داخل الرمز المميز الخاص بك. يمكن بعد ذلك فك تشفير هذه المعلومات من الرمز المميز بواسطة أي شخص لديه مفتاح فك التشفير. في مثالنا ، نقوم بترميز user.id بحيث عندما نتلقى الرمز المميز مرة أخرى في النهاية الخلفية للمصادقة ، فإننا نعرف المستخدم الذي نتعامل معه.
  • secretOrPrivateKey هو إما مفتاح سري مشترك لتشفير HMAC - وهذا ما استخدمناه في تطبيقنا ، من أجل التبسيط - أو مفتاح خاص لتشفير RSA / ECDSA.
  • تمثل options مجموعة متنوعة من الخيارات التي يمكن تمريرها إلى المشفر في شكل أزواج مفتاح - قيمة. عادةً ما نحدد على الأقل expiresIn (تصبح exp محجوزة) issuer ( iss محجوزة) بحيث لا يكون الرمز المميز صالحًا إلى الأبد ، ويمكن للخادم التحقق من أنه أصدر بالفعل الرمز المميز في الأصل.
  • callback هو وظيفة للاستدعاء بعد إجراء التشفير ، إذا رغب المرء في التعامل مع تشفير الرمز المميز بشكل غير متزامن.

(يمكنك أيضًا قراءة المزيد من التفاصيل حول options وكيفية استخدام تشفير المفتاح العام بدلاً من المفتاح السري المشترك.)

التكامل الزاوي 6 JWT

لجعل Angular 6 تعمل مع JWT الخاص بنا أمر بسيط للغاية باستخدام angular-jwt . ما عليك سوى إضافة ما يلي إلى client/src/app/app.modules.ts :

 import { JwtModule } from '@auth0/angular-jwt'; // ... export function tokenGetter() { return localStorage.getItem('access_token'); } @NgModule({ // ... imports: [ BrowserModule, AppRoutingModule, HttpClientModule, FormsModule, // Add this import here JwtModule.forRoot({ config: { tokenGetter: tokenGetter, whitelistedDomains: ['localhost:4000'], blacklistedRoutes: ['localhost:4000/api/auth'] } }) ], // ... }

هذا هو كل ما هو مطلوب في الأساس. بالطبع ، لدينا المزيد من التعليمات البرمجية لإضافتها لإجراء المصادقة الأولية ، لكن مكتبة angular-jwt تهتم بإرسال الرمز المميز مع كل طلب HTTP.

  • تقوم وظيفة tokenGetter() بما تقوله بالضبط ، ولكن كيفية تنفيذها متروك لك تمامًا. لقد اخترنا إعادة الرمز الذي نحفظه في localStorage . أنت حر بالطبع في تقديم أي طريقة أخرى تريدها ، طالما أنها تُرجع السلسلة المشفرة لرمز الويب JSON .
  • يوجد خيار whiteListedDomains حتى تتمكن من تقييد المجالات التي يتم إرسال JWT إليها ، لذلك لا تتلقى واجهات برمجة التطبيقات العامة JWT أيضًا.
  • يتيح لك خيار blackListedRoutes تحديد مسارات معينة لا ينبغي أن تستقبل JWT حتى لو كانت في نطاق مدرج في القائمة البيضاء. على سبيل المثال ، لا تحتاج نقطة نهاية المصادقة إلى استلامها لأنه لا توجد نقطة: عادةً ما يكون الرمز المميز فارغًا عندما يتم استدعاؤه على أي حال.

نجعل كل شيء يعمل معًا

في هذه المرحلة ، لدينا طريقة لإنشاء JWT لمستخدم معين باستخدام نقطة النهاية /auth على واجهة برمجة التطبيقات الخاصة بنا ، ولدينا أعمال السباكة على Angular لإرسال JWT مع كل طلب HTTP. رائع ، لكن يمكنك الإشارة إلى أنه لم يتغير شيء على الإطلاق للمستخدم. وأنك سوف تكون الصحيح. لا يزال بإمكاننا الانتقال إلى كل صفحة في تطبيقنا ، ويمكننا استدعاء أي نقطة نهاية لواجهة برمجة التطبيقات دون حتى إرسال JWT. ليست جيدة!

نحتاج إلى تحديث تطبيق العميل الخاص بنا لنشعر بالقلق بشأن من قام بتسجيل الدخول ، وكذلك تحديث واجهة برمجة التطبيقات لدينا لطلب JWT. هيا بنا نبدأ.

سنحتاج إلى مكون Angular جديد لتسجيل الدخول. من أجل الإيجاز ، سأبقي هذا بسيطًا قدر الإمكان. سنحتاج أيضًا إلى خدمة تتعامل مع جميع متطلبات المصادقة لدينا ، و Angular Guard لحماية المسارات التي لا ينبغي الوصول إليها قبل تسجيل الدخول. سنقوم بما يلي في سياق تطبيق العميل.

 cd client ng g component login --spec=false --inline-style ng g service auth --flat --spec=false ng g guard auth --flat --spec=false

من المفترض أن يؤدي هذا إلى إنشاء أربعة ملفات جديدة في مجلد client :

 src/app/login/login.component.html src/app/login/login.component.ts src/app/auth.service.ts src/app/auth.guard.ts

بعد ذلك ، نحتاج إلى توفير خدمة المصادقة والحراسة لتطبيقنا. تحديث client/src/app/app.modules.ts :

 import { AuthService } from './auth.service'; import { AuthGuard } from './auth.guard'; // ... providers: [ TodoService, UserService, AuthService, AuthGuard ],

ثم قم بتحديث التوجيه في client/src/app/app-routing.modules.ts للاستفادة من حارس المصادقة وتوفير مسار لمكون تسجيل الدخول.

 // ... import { LoginComponent } from './login/login.component'; import { AuthGuard } from './auth.guard'; const routes: Routes = [ { path: 'todos', component: TodoListComponent, canActivate: [AuthGuard] }, { path: 'users', component: UserListComponent, canActivate: [AuthGuard] }, { path: 'login', component: LoginComponent}, // ...

أخيرًا ، قم بتحديث client/src/app/auth.guard.ts بالمحتويات التالية:

 import { Injectable } from '@angular/core'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { constructor(private router: Router) { } canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) { if (localStorage.getItem('access_token')) { return true; } this.router.navigate(['login']); return false; } }

بالنسبة لتطبيقنا التجريبي ، نحن ببساطة نتحقق من وجود JWT في التخزين المحلي. في تطبيقات العالم الحقيقي ، يمكنك فك تشفير الرمز المميز والتحقق من صلاحيته وانتهائه وما إلى ذلك. على سبيل المثال ، يمكنك استخدام JwtHelperService لهذا الغرض.

في هذه المرحلة ، سيعيد تطبيق Angular الخاص بنا الآن توجيهك دائمًا إلى صفحة تسجيل الدخول نظرًا لعدم وجود طريقة لتسجيل الدخول. دعنا نصحح ذلك ، بدءًا من خدمة المصادقة في client/src/app/auth.service.ts :

 import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable() export class AuthService { constructor(private http: HttpClient) { } login(username: string, password: string): Observable<boolean> { return this.http.post<{token: string}>('/api/auth', {username: username, password: password}) .pipe( map(result => { localStorage.setItem('access_token', result.token); return true; }) ); } logout() { localStorage.removeItem('access_token'); } public get loggedIn(): boolean { return (localStorage.getItem('access_token') !== null); } }

خدمة المصادقة لدينا لها وظيفتان فقط ، login logout :

  • login POST هو username password المقدمين إلى access_token في localStorage إذا استعاد واحدًا. من أجل البساطة ، لا يوجد خطأ في المعالجة هنا.
  • يؤدي logout ببساطة إلى مسح access_token من localStorage ، مما يتطلب الحصول على رمز جديد قبل التمكن من الوصول إلى أي شيء آخر مرة أخرى.
  • loggedIn هي خاصية منطقية يمكننا استخدامها بسرعة لتحديد ما إذا كان المستخدم قد قام بتسجيل الدخول أم لا.

وأخيرًا ، مكون تسجيل الدخول. لا علاقة لها بالعمل مع JWT ، لذا لا تتردد في النسخ واللصق في client/src/app/login/login.components.html :

 <h4 *ngIf="error">{{error}}</h4> <form (ngSubmit)="submit()"> <div class="form-group col-3"> <label for="username">Username</label> <input type="text" name="username" class="form-control" [(ngModel)]="username" /> </div> <div class="form-group col-3"> <label for="password">Password</label> <input type="password" name="password" class="form-control" [(ngModel)]="password" /> </div> <div class="form-group col-3"> <button class="btn btn-primary" type="submit">Login</button> </div> </form>

وسيحتاج client/src/app/login/login.components.ts إلى:

 import { Component, OnInit } from '@angular/core'; import { AuthService } from '../auth.service'; import { Router } from '@angular/router'; import { first } from 'rxjs/operators'; @Component({ selector: 'app-login', templateUrl: './login.component.html' }) export class LoginComponent { public username: string; public password: string; public error: string; constructor(private auth: AuthService, private router: Router) { } public submit() { this.auth.login(this.username, this.password) .pipe(first()) .subscribe( result => this.router.navigate(['todos']), err => this.error = 'Could not authenticate' ); } }

Voila ، مثالنا لتسجيل الدخول إلى Angular 6:

شاشة تسجيل الدخول لنموذج تطبيق Angular Todo List الخاص بنا.

في هذه المرحلة ، يجب أن نكون قادرين على تسجيل الدخول (باستخدام jemma أو paul أو sebastian بكلمة المرور todo ) ورؤية جميع الشاشات مرة أخرى. لكن تطبيقنا يعرض نفس رؤوس التنقل ولا توجد طريقة لتسجيل الخروج بغض النظر عن الحالة الحالية. دعنا نصلح ذلك قبل أن ننتقل إلى إصلاح API الخاص بنا.

في client/src/app/app.component.ts ، استبدل الملف بالكامل بما يلي:

 import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from './auth.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { constructor(private auth: AuthService, private router: Router) { } logout() { this.auth.logout(); this.router.navigate(['login']); } }

وبالنسبة إلى client/src/app/app.component.html ، استبدل قسم <nav> بما يلي:

 <nav class="nav nav-pills"> <a class="nav-link" routerLink="todos" routerLinkActive="active" *ngIf="auth.loggedIn">Todo List</a> <a class="nav-link" routerLink="users" routerLinkActive="active" *ngIf="auth.loggedIn">Users</a> <a class="nav-link" routerLink="login" routerLinkActive="active" *ngIf="!auth.loggedIn">Login</a> <a class="nav-link" (click)="logout()" href="#" *ngIf="auth.loggedIn">Logout</a> </nav>

لقد جعلنا سياق التنقل الخاص بنا مدركًا أنه يجب أن يعرض عناصر معينة فقط اعتمادًا على ما إذا كان المستخدم قد قام بتسجيل الدخول أم لا. يمكن auth.loggedIn ، بالطبع ، في أي مكان يمكنك فيه استيراد خدمة المصادقة.

تأمين API

قد تفكر ، هذا رائع ... كل شيء يبدو أنه يعمل بشكل رائع . لكن حاول تسجيل الدخول باستخدام أسماء المستخدمين الثلاثة المختلفة ، وستلاحظ شيئًا ما: كلهم ​​يعرضون نفس قائمة المهام. إذا ألقينا نظرة على خادم API الخاص بنا ، يمكننا أن نرى أن كل مستخدم لديه ، في الواقع ، قائمة من العناصر الخاصة به ، فما الأمر؟

حسنًا ، تذكر عندما بدأنا ، قمنا بترميز نقطة نهاية API /todos لإرجاع قائمة المهام userID=1 . كان هذا لأنه لم يكن لدينا أي طريقة لمعرفة من هو المستخدم الذي قام بتسجيل الدخول حاليًا.

الآن نقوم بذلك ، لذلك دعونا نرى مدى سهولة تأمين نقاط النهاية الخاصة بنا واستخدام المعلومات المشفرة في JWT لتوفير هوية المستخدم المطلوبة. في البداية ، أضف هذا السطر إلى ملف server/app.js أسفل آخر app.use() :

 app.use(expressJwt({secret: 'todo-app-super-shared-secret'}).unless({path: ['/api/auth']}));

نحن نستخدم البرمجيات الوسيطة express-jwt ، ونخبرها ما هو السر المشترك ، ونحدد مجموعة من المسارات التي لا ينبغي أن تتطلب JWT لها. وهذا كل شيء. لا حاجة للمس كل نقطة نهاية ، if إنشاء عبارات في كل مكان ، أو أي شيء.

داخليًا ، تقوم البرامج الوسيطة ببعض الافتراضات. على سبيل المثال ، يفترض أن رأس HTTP Authorization يتبع نمط JWT الشائع لـ Bearer {token} . (تحتوي المكتبة على الكثير من الخيارات لتخصيص كيفية عملها إذا لم يكن الأمر كذلك. راجع استخدام Express-jwt لمزيد من التفاصيل.)

هدفنا الثاني هو استخدام المعلومات المشفرة في JWT لمعرفة من يقوم بالاتصال. مرة أخرى يأتي express-jwt لإنقاذ. كجزء من قراءة الرمز المميز والتحقق منه ، يقوم بتعيين الحمولة المشفرة التي أرسلناها في عملية التوقيع إلى المتغير req.user في Express. يمكننا بعد ذلك استخدامه للوصول فورًا إلى أي من المتغيرات التي قمنا بتخزينها. في حالتنا ، قمنا بتعيين معرف userID مساويًا لمعرف المستخدم المصادق عليه ، وبالتالي يمكننا استخدامه مباشرة كـ req.user.userID .

قم بتحديث server/app.js مرة أخرى ، وقم بتغيير نقطة نهاية /todos لتقرأ كما يلي:

 res.send(getTodos(req.user.userID)); 

يستفيد تطبيق Angular Todo List الخاص بنا من JWT لإظهار قائمة المهام للمستخدم الذي قام بتسجيل الدخول ، بدلاً من القائمة التي قمنا بترميزها مسبقًا.

وهذا كل شيء. واجهة برمجة التطبيقات الخاصة بنا مؤمنة الآن ضد الوصول غير المصرح به ، ويمكننا بأمان تحديد هوية المستخدم المصادق عليه في أي نقطة نهاية. يحتوي تطبيق العميل الخاص بنا أيضًا على عملية مصادقة بسيطة ، وأي خدمات HTTP نكتبها تستدعي نقطة نهاية API الخاصة بنا سوف يتم إرفاقها تلقائيًا برمز المصادقة المميز.

إذا قمت باستنساخ مستودع جيثب ، وأردت ببساطة أن ترى النتيجة النهائية قيد التنفيذ ، فيمكنك التحقق من الكود في شكله النهائي باستخدام:

 git checkout with-jwt

آمل أن تكون قد وجدت هذه الإرشادات مفيدة لإضافة مصادقة JWT إلى تطبيقات Angular الخاصة بك. شكرا للقراءة!

ذات صلة: تعليمي JSON Web Token: مثال في Laravel و AngularJS