كيفية القيام بمصادقة Authentication المستخدمين في ASP.NET Core Identity

-

بنتعلم في هذا الدرس شو يعني Authentication والي هي عملية إنشاء identity للمستخدم. حتي نقدر ندخل الى التطبيق يجب تقديم credentials (اسم المستخدم وكلمة المرور).واكيد حتي نكون قادرين على استخدام التطبيق يجب توفير بيانات الاعتماد الصحيحة له من خلال صفحة تسجيل الدخول. هذه العملية تسمى authenticate  

بنكمل شغل على مشروعنا ASP.NET.Core.Identity 

تمام نبدأ اول اشي بانشاء Controller باسم HomeController في مجلد Controllers ونغير الكود index action الى :

using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace ASP.NET.Core.Identity.Controllers {     public class HomeController : Controller     {         public IActionResult Index()         {             return View((object)"Hello");        }    }}

الان نعمل View لهذا action لذا انقر يمين فوق action وبعدها اختر Add New View ونعدل الكود الي: 

@model string @{    ViewData["Title"] = "Home";}<h3 class="bg-gradient text-black">Home Index</h3>@Model 

شغل الصفحه وبتشوف النتيجة ان تم ارجاع كلمة Hello في View فقط. 

https://localhost:44347/home


الهدف من هذا المثال هو نتذكر كيف بتم استدعاء index action بدون اي قيود او شروط، بحيث يتم عند طلب هذا action أرسال طلب من المتصفح un-authenticated request وبالتالي بتكون النتيجة عرض الرسالة في المتصفح. 

طيب شو لو كان المطلوب منع الدخول الى الصفحه من قبل اي احد يعني الازم الشخص الي بقدر يدخل الى الصفحه معو صلاحيات Authenticated. حتي نعمل هذه الحركة يجب تعديل الكود الي: 

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASP.NET.Core.Identity.Controllers
{
    public class HomeController : Controller
    {
        [Authorize]
        public IActionResult Index()
        {
            return View((object)"Hello");
        }
    }
}

لاحظ ان اضفنا كلمة [Authorize]  والي بتعني ان هذا action لا يسمح بالدخول له الا عن اذا كان عند المستخدم صلاحيات. 

جرب شغل الصفحة واكيد بتكون النتيجة.

 

لاحظ الخطأ هو : The localhost page can’t be found. HTTP ERROR 404

وتأكد من الرابط في URL اكيد بتشوف ان تم ارجاع المتصفح الى الرابط : 

https://localhost:44347/Account/Login?ReturnUrl=%2F. 

وسبب هذا الخطأ هو اضافة الكود [Authorize] الي بيعني ان ممنوع دخول هذا action الا في حال كان الشخص عندو صلاحيات. والرابط الي رجعنا عليه هو الرابط الافتراضي لصفحة تسجيل الدخول. 

في URL السابق يوجد 2F% والي بيعني عنوان مشفر encoded URL  يشير الى / 

تغيير عنوان الدخول الافتراضي Login URL 

شفنا في المثال السابق ان عنوان الدخول الافتراض هو Account/Login/ لكن شو لو كان المطلوب استخدام عنوان آخر. اكيد بنقدر نغيير هذا العنوان. ولتغير العنوان 

ملاحظة: العنوان الافتراضي  Account/Login/ يستخدم في حال وجود authorization . 

تمام نتعلم كيف بنغير العنوان الافتراضي :

ننتقل الى ()ConfigureServices  في ملف Startup ونضيف الكود التالي: 

services.ConfigureApplicationCookie(opts => opts.LoginPath = "/Login");

وبهاي الحالة بكون العنوان الي بنرجع عليه هو : 

https://localhost:44347/Login?ReturnUrl=%2F

شغل الصفحه وشوف النتيجة 



تمام الان خلونا نعمل صفحة Login لتطبيق Authentication

اول اشي بنحتاج الى انشاء class باسم Login.cs في مجلد Models ونضيف الكود التالي:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace ASP.NET.Core.Identity.Models
{
    public class Login
    {
        [Required]
        public string Email { get; set; }
        [Required]
        public string Password { get; set; }
        public string ReturnUrl { get; set; }
    }
}

في هذا class اضفنا بيانات الدخول الي هي الايميل وكلمة المرور واضفنا ReturnUrl والي بتحتوي على عنوان URL الراجع الي حدده نظام Identity.

نتذكر ان حدننا عنوان URL في ()ConfigureServices بالشكل :
https://localhost:44347/Login

بناء عليه اكيد نحنا بحاجه الى انشاء هذا URL يعني بنحتاج الى انشاء controller و Action. تمام ننتقل الى مجلد Controllers ونضيف controller باسم LoginController ونضيف الكود التالي: 

using ASP.NET.Core.Identity.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASP.NET.Core.Identity.Controllers
{
    [Authorize]
    public class LoginController : Controller
    {
        private UserManager<AppUser> userManager;
        private SignInManager<AppUser> signInManager;
        public LoginController(UserManager<AppUser> userMgr, SignInManager<AppUser> signinMgr)
        {
            userManager = userMgr;
            signInManager = signinMgr;
        }
       
        [AllowAnonymous]
        public IActionResult Index(string returnUrl)
        {
            Login login = new Login();
            login.ReturnUrl = returnUrl;
            return View(login);
        }
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Index(Login login)
        {
            if (ModelState.IsValid)
            {
                AppUser appUser = await userManager.FindByEmailAsync(login.Email);
                if (appUser != null)
                {
                    await signInManager.SignOutAsync();
                    Microsoft.AspNetCore.Identity.SignInResult result = await signInManager.PasswordSignInAsync(appUser, login.Password, false, false);
                    if (result.Succeeded)
                        return Redirect(login.ReturnUrl ?? "/");
                }
                ModelState.AddModelError(nameof(login.Email), "Login Failed: Invalid Email or password");
            }
            return View(login);
        }
    }
}

تمام نفهم الكود : 

سمينا action باسم index لان الرابط الافتراضي ل Controller بيبدا ب index وهو الرابط الي عرفناه في ()ConfigureServices

في البداية اضفنا [Authorize] attribute الي من خلالها بنتحكم Controller بحيث لا يمكن استدعاؤه بواسطة الطلبات غير المصادق عليها un-authenticated .لكن اكيد نحنا بحاجه الى الوصل الى Index action مشان هيك أضفنا سمات [AllowAnonymous] attribute إلى Index Action Methods  بالي من خلالها يمكن استدعاء action methods من خلال الطلبات غير المصادق عليها un-authenticated.

واضفنا  تبعية باستخدام الكود 

private UserManager<AppUser> userManager;
private SignInManager<AppUser> signInManager;

وبعدها في constructor اضفنا هذه المتغيرات على شكل objects بواسطة ميزة Dependency Injection في ASP.NET Core. الكود: 

public LoginController(UserManager<AppUser> userMgr, SignInManager<AppUser> signinMgr)
        {
            userManager = userMgr;
            signInManager = signinMgr;
        }

يتم استخدام UserManager لإدارة المستخدمين في Identity بينما يتم استخدام SignInManager لإجراء مصادقة authentication المستخدمين.

بعد هيك أضفنا Index action الاول من نوع HTTP Get وطبقنا [AllowAnonymous]  عليها بحيث لا تتطلب المصادقة authentication. والسبب في هذا ان المطلوب من جميع المستخدمين الوصول الى هذه الشاشة لادخال بيانات الدخول.

واكيد لاحظت ان هذا الإجراء يحتوي على parameter باسم returnUrl الي بيرجع من  query sting وبعد هيك بنخزن قيمة هذا parameter  في متغير ReturnUrl الخاص ب بواسطة تقنية Model Binding لـ ASP.NET Core. الكود :

 [AllowAnonymous]
        public IActionResult Index(string returnUrl)
        {
            Login login = new Login();
            login.ReturnUrl = returnUrl;
            return View(login);
        }

اضفنا كمان Index action من نوع [HttpPost] الي من خلالو بنعمل على التحقق من بيانات الدخول للمستخدم واذا كانت صحيحه بنكمل اذا لا بنرجع رسالة خطأ. الكود: 

[HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Index(Login login)
        {
            if (ModelState.IsValid)
            {
                AppUser appUser = await userManager.FindByEmailAsync(login.Email);
                if (appUser != null)
                {
                    await signInManager.SignOutAsync();
                    Microsoft.AspNetCore.Identity.SignInResult result = await signInManager.PasswordSignInAsync(appUser, login.Password, false, false);
                    if (result.Succeeded)
                        return Redirect(login.ReturnUrl ?? "/");
                }
                ModelState.AddModelError(nameof(login.Email), "Login Failed: Invalid Email or password");
            }
            return View(login);
        }

في هذا action اضفنا parameter ، من نوع Login class ، والتي بتم ارسال قيمها من خلال View تسجيل الدخول (البريد الإلكتروني وكلمة المرور) التي يملأها المستخدم في نموذج تسجيل الدخول login form. تم تزويد هذا Action ب 2 attributes وهم

  • [AllowAnonymous] attribute الي بتعني ان هذا action يمكن استدعاؤه بواسطة الطلبات غير المصدق عليها un-authenticated.
  • [ValidateAntiForgeryToken]  لمنع تزوير الطلبات عبر المواقع.
بعد هيك بنبحث عن بيانات المستخدم باسخدام FindByEmailAsync() action عن طريق الايميل (الكود)
AppUser appUser = await userManager.FindByEmailAsync(login.Email);
بعد هيك بنفحص قيمة appUser التي تم تلقيها على AppUser object اذا ما كانت فارغة. في هذه الحالة ، نقوم أولاً بتسجيل الخروج من أي مستخدم قام بتسجيل الدخول من التطبيق:
 await signInManager.SignOutAsync();

ثم بنستخدم PasswordSignInAsync action ل SignInManager class الي من خلالها بتم تسجيل دخول المستخدم بعد التحقق من كلمة المرور وبنرسل AppUser object وكلمة المرور الموجودة في login.Password.

Microsoft.AspNetCore.Identity.SignInResult result = await signInManager.PasswordSignInAsync(appUser, login.Password, false, false);

لاحظ ان ارسلنا القيم False في Parameter رقم 3 و 4  والسبب ان ما بدنا نستخدم ملف cookie دائم لتسجيل الدخول (حفظ بيانات المستخدم يظل حتى بعد إغلاق المتصفح) ، ولا أريد قفل الحساب عند فشل تسجيل الدخول.

النتيجة بتتخزن في SignInResult object  الذي يحتوي على نتيجة إجراء تسجيل الدخول. تحتوي الخاصية " true  " على قيمة حقيقية إذا كان تسجيل الدخول ناجحًا ، وغير ذلك فتحتوي على "false".

واخر اشي بنتحقق من القيمة الراجعه من SignInResult وبناء على النتيجة بنتخذ القرار اذا كانت النتيجة Succeeded سنعيد المستخدم إلى عنوان URL الموجود في خاصية ReturnUrl ل Login class. الكود:

 if (result.Succeeded)
     return Redirect(login.ReturnUrl ?? "/");
واذا كانت النتيجة غير ذلك بنرجع رسالة Login Failed: Invalid Email or password   الكود :

ModelState.AddModelError(nameof(login.Email), "Login Failed: Invalid Email or password");

انشاء Login View 

بيانات الدخول الي بيرسلها المستخدم بتم من خلال شاشة الدخول، مشان هيك خلونا نعمل شاشة تسجل الدخول بانشاء View. ننتقل الى Index action ونعمل View بالنقر يمين فوق action ونختار Add New View وبعدها نضيف الكود: 

@model Login @{     ViewData["Title"] = "Login ";     var returnUrl = @Context.Request.Query["returnurl"]; } <h3 class="bg-opacity-10 text-black">Login</h3> <div class="text-danger" asp-validation-summary="All"></div> <form  method="post" asp-route-returnurl="@returnUrl">     <input type="hidden" asp-for="ReturnUrl" />     <div class="form-group">         <label asp-for="Email"></label>         <input asp-for="Email" class="form-control" />     </div>     <div class="form-group">         <label asp-for="Password"></label>         <input asp-for="Password" class="form-control" /> </div>  <br />     <button class="btn btn-primary" type="submit">Log In</button> </form>
نشغل التطبيق ونشوف النتيجة. 
اجباري تفتح صفحة تسجيل الدخول التالية بالرغم ان طلبنا صفحة Home :


تذكر في الدرس السابق اضفنا اسم مستخدم بالبيانات 

الاسم : layan

الايميل : [email protected]

كلمة المرور:Layan@@12%% 

نجرب نسخدم هاي البيانات في الدخول ونشوف النتيجة .


اجباري اذا كان اسم المستخدم وكلمة المرور صحيح بتم العملية بنجاح وبنرجع الى صفحة Home. جرب وشوف النتيجة. 


تمام الان خلونا نعرض بيانات المستخدم في الصفحة بعد الدخول بنجاح.

الهدف من هاي الحركة هو عرض اسم المستخدم الحالي بمكان ما في الصفحه، برسالة ترحيب مثلا. 

حتي نرجع بيانات المستخدم الحالي بنستخدم الامر : ()GetUserAsync الي هي موجوده في UserManager class  والكود المسؤول عن هذه العملية هو : 

AppUser user = await userManager.GetUserAsync(HttpContext.User);

نرجع ل HomeController ونعدل على الكود الى : 

  

using ASP.NET.Core.Identity.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASP.NET.Core.Identity.Controllers
{
    public class HomeController : Controller
    {
        private UserManager<AppUser> userManager;
        public HomeController(UserManager<AppUser> userMgr)
        {
            userManager = userMgr;
        }
        [Authorize]
        public async Task<IActionResult> Index()
        {
            AppUser user = await userManager.GetUserAsync(HttpContext.User);
            string message = "Welcome, " + user.UserName;
            return View((object)message);
        }
    }
}

نفهم شو عملنا. 

عملنا Instance باسم userManager من نوع <UserManager<AppUser  واستخدمنا هذا Instance في Constructor. وبعدها في Index action رجعنا اسم المستخدم باستخدام الكود: 

 AppUser user = await userManager.GetUserAsync(HttpContext.User);

وخزنا القيمة في متغير باسم message وبعدها رجعنا المسج ك object الى View 

شغل الصفحة وشوف النتيجة (تم عرض الاسم باول الصفحه برسالة Welcome , Layan)

كيف بنعمل sign-out  من التطبيق 

اكيد لازم تضيف في التطبيق زر لتسجيل الخروج خاصة حتي نعطي المستخدم امكانية الخروج من التطبيق). وحتي نعمل هاي الحركة ستقوم ميزة تسجيل الخروج ببساطة بتسجيل خروج المستخدم الموقع من Identity،وحتى نعمل هاي الميزة لتسجيل الخروج. لذا خلونا نضيف Logout action إلى HomeController.cs ونعدل الكود الي:

using ASP.NET.Core.Identity.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASP.NET.Core.Identity.Controllers
{
    public class HomeController : Controller
    {
        private UserManager<AppUser> userManager;
        private SignInManager<AppUser> signInManager;
        public HomeController(UserManager<AppUser> userMgr, SignInManager<AppUser> signinMgr)
        {
            userManager = userMgr;
            signInManager = signinMgr;
        }
        [Authorize]
        public async Task<IActionResult> Index()
        {
            AppUser user = await userManager.GetUserAsync(HttpContext.User);
            string message = "Welcome, " + user.UserName;
            return View((object)message);
        }
        public async Task<IActionResult> Logout()
        {
            await signInManager.SignOutAsync();
            return RedirectToAction("Index", "Home");
        }

    }
}
اضفنا Instance باسم signInManager من نوع <SignInManager<AppUser الي من خلالو بنعمل تسجيل الخروج واضفنا action للخروج الكود:

 public async Task<IActionResult> Logout()
        {
            await signInManager.SignOutAsync();
            return RedirectToAction("Index", "Home");
        }

تمام اكيد لتنفيذ هاذ الامر بنحتاج الى اضافة زر في HomeController الي من خلالو بنستدعي Logout action . مشان هيك خلونا نرجع الى Index View في HomeController  ونضيف الكود التالي :

@model string
@{
    ViewData["Title"] = "Home";
}
<h3 class="bg-gradient text-black">Home Index</h3>
@Model
@if (User?.Identity?.IsAuthenticated ?? false)
{
<a asp-controller="Home" asp-action="Logout" class="btn btn-danger">sign-out</a>
}

استخدمنا RazorPageBase class من Microsoft.AspNetCore.Mvc.Razor namespace ، الي من خلالو استخدمنا User.Identity.IsAuthenticated property  للتأكد من حالة المستخدم اذا كان مسجل دخول الى التطبيق او لا. بحيث اذا كان مسجل الدخول بنعرض زر تسجيل الخرزج.

شغل الصفحة وشوف النتيجة: 


حاول تضغط على الزر Sign-out واكيد بعد تسجيل الخروج بترجع لصفحة Index في Home ، حاول ترجع من المتصفح صفحة سابقة اكيد برجعك لصفحة تسجيل الدخول.


استخدام Identity Cookie

بمجرد تسجيل الدخول بنجاح authenticated ستقوم Identity بتخزين ملف cookie  في المتصفح. وبكون اسم cookie هذا هو AspNetCore.Identity.Application. يمكن رؤية ملف cookie هذا في Application tab بأدوات developer في متصفح Chrome. 



يتم إرسال ملف cookie هذا إلى Server عند كل طلب HTTP ، كما هو الحال عند فتح أي عنوان URL للتطبيق في المتصفح. يستخدم ASP.NET Core Identity  ال cookie  هذا لتحديد ما إذا كان المستخدم مصادقًا authenticated  أم لا.

ملاحظة - يمكن تسجيل خروج أي مستخدم من Identity عن طريق حذف ملف cookie.

جرب تحذف الملف وعمل refresh للصفحة وشوف النتيجة، اكيد بترجع للصفحة الرئيسية.




تحديد وقت لانتهاء Identity Cookie Timeout

يمكنك التحكم بوقت انتهاء صلاحية ملف Identity Cookie  باستخدام ConfigureApplicationCookie method  ل IServiceCollection interface لذا خلونا ننتقل الى ملف Startup ونعدل على ConfigureServices ونضيف الكود التالي:

 services.ConfigureApplicationCookie(options =>
            {
                options.Cookie.Name = ".AspNetCore.Identity.Application";
                options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
                options.SlidingExpiration = true;
            });

هذا الكود بيعني ان سيتم انتهاء صلاحية هذا ملف Identity Cookie  بعد 20 دقيقة.