আজকের লেকচারে স্বাগতম!

কোথায় আছি আমরা? আপনি বর্তমানে Section 25: Identity-তে আছেন। গত লেকচারে আমরা Authorization এনাবল করেছিলাম, যার ফলে লগইন ছাড়া কেউ ওয়েবসাইটের ভেতরের পেজগুলোতে যেতে পারতো না। কিন্তু এতে একটি ছোট সমস্যা তৈরি হয়েছিলো—লগইন করার পর সিস্টেম ইউজারকে সবসময় একটি নির্দিষ্ট পেজে (যেমন: Index) পাঠিয়ে দিতো, সে যে পেজেই যেতে চাক না কেন। আজকে আমরা শিখবো ReturnUrl ব্যবহার করে ইউজারকে ঠিক তার কাঙ্ক্ষিত পেজেই কীভাবে রিডাইরেক্ট করতে হয়। চলুন শুরু করি!


📝 Quick Summary for Revision

ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য পুরো লেকচারের মূল বিষয়গুলো নিচে লিস্ট করা হলো:

  • ReturnUrl: এটি একটি Query String Parameter, যা লগইন করার আগে ইউজার যে URL-এ যেতে চেয়েছিলো, তা স্টোর করে রাখে।
  • URL Encoding: URL-এ থাকা %2F মানে হলো ফরোয়ার্ড স্ল্যাশ (/)।
  • View Update: ফর্ম সাবমিট করার সময় ReturnUrl-কে POST রিকোয়েস্টের সাথে সার্ভারে পাঠানোর জন্য <form> ট্যাগে asp-route-returnUrl ব্যবহার করতে হয়।
  • Controller Update: POST Login মেথডে string returnUrl নামে একটি প্যারামিটার রিসিভ করে লগইন সাকসেসফুল হলে সেখানে রিডাইরেক্ট করতে হয়।
  • Security (LocalRedirect): হ্যাকারদের (Open Redirect Attack) থেকে বাঁচার জন্য সবসময় সাধারণ Redirect() এর বদলে LocalRedirect() ব্যবহার করতে হয়।

🧠 Comprehensive Breakdown

এখানে লেকচারের প্রতিটি বিষয় বিস্তারিতভাবে এবং ধাপে ধাপে ব্যাখ্যা করা হলো:

১. ReturnUrl কী এবং কেন প্রয়োজন? (Priority: 10/10)

ধরুন, আপনি সরাসরি www.yoursite.com/Countries/UploadFromExcel লিংকে ঢোকার চেষ্টা করলেন। কিন্তু আপনি লগইন করা নেই। তখন Authorization Middleware আপনাকে আটকে দিবে এবং লগইন পেজে পাঠিয়ে দিবে। লগইন পেজের URL-টি তখন দেখতে এমন হবে: .../Account/Login?ReturnUrl=%2FCountries%2FUploadFromExcel

এখানে ReturnUrl হলো সেই Query String যা আপনার অরিজিনাল ডেস্টিনেশন মনে রেখেছে (এখানে %2F মানে /)। একজন ভালো ইউজারের এক্সপেরিয়েন্স হলো, সে লগইন করার সাথে সাথেই যেন তাকে ওই UploadFromExcel পেজেই নিয়ে যাওয়া হয়, অন্য কোথাও নয়।

২. The HTTP Request Flow (How it works internally) (Priority: 9/10)

পুরো প্রক্রিয়াটি সার্ভার এবং ক্লায়েন্টের মাঝে কীভাবে কাজ করে তা বোঝা খুব জরুরি।

  1. Step 1: ইউজার একটি সিকিউর পেজ (যেমন: /persons/index) রিকোয়েস্ট করে। সার্ভার দেখে ইউজার লগইন করা নেই, তাই সে একটি HTTP 302 Found রেসপন্স পাঠায় এবং Location সেট করে দেয় /Account/Login?ReturnUrl=/persons/index
  2. Step 2: ব্রাউজার ওই Login পেজে যায় এবং ইউজার ফর্ম ফিলাপ করে Submit করে (POST Request)।
  3. Step 3: এই POST রিকোয়েস্টের সাথেও ওই ReturnUrl সার্ভারে যায়। সার্ভার লগইন সাকসেসফুল করে আবার একটি HTTP 302 রেসপন্স দেয়, তবে এবার Location থাকে সেই অরিজিনাল ReturnUrl (অর্থাৎ /persons/index)।

৩. Step 1: Updating the Login View (Priority: 10/10)

যখন ইউজার লগইন ফর্মে সাবমিট বাটনে ক্লিক করবে, তখন POST রিকোয়েস্টের সাথে ওই ReturnUrl যেন Controllers-এ যায়, তার ব্যবস্থা আমাদের View-তে করতে হবে।

💻 Code Implementation (Views/Account/Login.cshtml):

@* Context.Request.Query থেকে বর্তমান URL-এর ReturnUrl পড়া হচ্ছে এবং ফর্মের অ্যাকশনে জুড়ে দেওয়া হচ্ছে *@
<form asp-controller="Account" asp-action="Login" asp-route-ReturnUrl="@Context.Request.Query["ReturnUrl"]" method="post">
    
    <button type="submit" class="btn btn-primary">Login</button>
</form>
 

Why do this? Context.Request.Query["ReturnUrl"] বর্তমান পেজের URL থেকে ভ্যালুটি ডাইনামিকালি পড়ে নেয়। asp-route-ReturnUrl ট্যাগ হেল্পারটি ফর্ম সাবমিট করার সময় এই ভ্যালুটিকে অ্যাকশন মেথডে পাঠিয়ে দেয়।

৪. Step 2: Receiving ReturnUrl in Controller (Priority: 10/10)

এখন আমাদের AccountController-এর HTTP POST Login মেথডটি আপডেট করতে হবে, যেন সে এই প্যারামিটারটি রিসিভ করতে পারে।

💻 Code Implementation (AccountController.cs):

[HttpPost]
// Model Binding এর মাধ্যমে URL থেকে returnUrl প্যারামিটারে ডেটা চলে আসবে
public async Task<IActionResult> Login(LoginDTO loginDTO, string? returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(loginDTO);
    }
 
    var result = await _signInManager.PasswordSignInAsync(loginDTO.Email, loginDTO.Password, isPersistent: false, lockoutOnFailure: false);
 
    if (result.Succeeded)
    {
        // 1. চেক করা হচ্ছে ReturnUrl এ কোনো ভ্যালু আছে কিনা এবং সেটি লোকাল URL কিনা
        if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
        {
            // 2. অরিজিনাল পেজে রিডাইরেক্ট করা হচ্ছে
            return LocalRedirect(returnUrl); 
        }
 
        // 3. যদি ReturnUrl না থাকে (ইউজার সরাসরি লগইন পেজে এসে থাকে), তবে ডিফল্ট পেজে যাবে
        return RedirectToAction(nameof(Index), "Persons");
    }
    
    ModelState.AddModelError("Login", "Invalid email or password");
    return View(loginDTO);
}
 

৫. Security Concept: Why LocalRedirect? (Priority: 10/10)

আপনি চাইলে সাধারণ Redirect(returnUrl) ব্যবহার করতে পারতেন। কিন্তু লেকচারার এখানে LocalRedirect() ব্যবহার করেছেন। কেন?

এটি Open Redirect Attack থেকে বাঁচার জন্য করা হয়। ধরুন আপনার ওয়েবসাইটের নাম abc.com। কোনো হ্যাকার ইউজারকে একটি ফিশিং লিংক দিলো: abc.com/Account/Login?ReturnUrl=http://hacker-website.com। ইউজার ভাবলো সে তো ট্রাস্টেড সাইটেই লগইন করছে। কিন্তু সাধারণ Redirect ব্যবহার করলে লগইন করার পরপরই ইউজার ওই হ্যাকারের সাইটে চলে যাবে।

LocalRedirect() বা Url.IsLocalUrl() চেক করে যে, ReturnUrl-এর ভ্যালুটি কি একই ডোমেইনের (Local) নাকি বাইরের কোনো ওয়েবসাইটের। যদি বাইরের কোনো ওয়েবসাইটের হয়, তবে এটি সরাসরি এরর থ্রো (throw) করবে এবং ইউজারকে সিকিউর রাখবে।


🌟 Best Practices & Smarter Updates (.NET 10 Context)

  • Always Validate URLs: লেকচারার যে Url.IsLocalUrl(returnUrl) চেকটি দেখিয়েছেন, এটি শুধু ASP.NET Core-এই নয়, বরং যেকোনো ওয়েব ফ্রেমওয়ার্কে সিকিউরিটির জন্য একটি গোল্ডেন রুল (Golden Rule)।
  • Use Hidden Field Alternative: অনেক মডার্ন প্রজেক্টে Query String-এর বদলে Form-এর ভেতরে একটি Hidden Field ব্যবহার করে ReturnUrl পাঠানো হয়।
<input type="hidden" name="ReturnUrl" value="@Context.Request.Query["ReturnUrl"]" />
 

উভয় পদ্ধতিই সঠিক, তবে asp-route ট্যাগ হেল্পারটি কোডকে অনেক বেশি ক্লিন রাখে।

  • Minimal API Note: আপনি যদি ভবিষ্যতে Minimal APIs নিয়ে কাজ করেন, সেখানেও আপনি মেথড সিগনেচারে [FromQuery] string? returnUrl ব্যবহার করে একইভাবে ভ্যালু রিসিভ এবং প্রসেস করতে পারবেন।

অসাধারণ! আপনার প্রজেক্টের লগইন সিস্টেম এখন পুরোপুরি সিকিউর এবং ইউজার-ফ্রেন্ডলি। পরবর্তী লেকচারে আমরা ইউজারদের Roles (যেমন: Admin, Manager, User) নিয়ে কাজ শুরু করবো!