Back to: ASP.NET Core Identity Tutorials
Redirect to ReturnUrl After Login in ASP.NET Core
In this article, I will discuss how to redirect to ReturnUrl After Login in ASP.NET Core. Please read our previous article discussing Custom Password Policy in ASP.NET Core Identity.
When users try to access a protected page without being logged in, ASP.NET Core automatically redirects them to a login page. To improve user experience, after successful login, users should be sent back to the page they originally wanted to visit. This is done using a special parameter called ReturnUrl, which stores the original page URL. Proper handling of this parameter ensures smooth navigation and prevents security risks like open redirect attacks.
What is Redirect to ReturnUrl After Login?
When a user attempts to access a restricted or protected page on your website that requires authentication but is not logged in, the application redirects them to the login page. After they successfully log in, it is a great user experience to redirect them back to the original page they wanted to visit. This is called redirecting to the ReturnUrl.
This behaviour prevents users from having to manually navigate back to their desired page after login, saving time and avoiding confusion.
How Does ASP.NET Core Handle This by Default?
Let us understand this with an example. When an unauthenticated user tries to access a protected resource, ASP.NET Core automatically redirects them to the login page. For example, now, click on the Secure link, which requires the user to be authenticated to access the Secure page.
Once you click on the above link, as you have not yet logged in, it will redirect you to the login page, as shown in the image below. This redirection includes a query string parameter named ReturnUrl, which holds the URL of the original page the user was trying to access. For example, if the user attempts to visit /Home/SecureMethod, they are redirected to.
The ReturnUrl query parameter name is fixed by ASP.NET Core Identity and cannot be changed. Your login page and login processing logic must be aware of this ReturnUrl to redirect the user back after login. Now, once you provide the credentials and log in, it will redirect you back to the original page, as shown below:
Key ASP.NET Core Attributes Used in This Scenario
- [Authorize]: Specifies that the action or controller requires an authenticated user to access.
- [AllowAnonymous]: Specifies that an action or controller allows unauthenticated users to access, even if the controller or parent has [Authorize].
Modifying Home Controller:
To understand this better, please modify the Home Controller as follows. Here, you can see we have two action methods decorated with Authorize and AllowAnonymous attributes. Authenticated and Anonymous users can access the NonSecureMethod action method. On the other hand, authenticated users can only access the SecureMethod method as this method is decorated with the Authorize Attribute.
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ASPNETCoreIdentityDemo.Controllers { public class HomeController : Controller { public IActionResult Index() { return View(); } public IActionResult Privacy() { return View(); } [AllowAnonymous] public IActionResult NonSecureMethod() { return View(); } [Authorize] public IActionResult SecureMethod() { return View(); } } }
Creating Views:
We already have the Index and Privacy view files in our application, which were, by default, created when we created the project using the Model-View-Controller Project template. So, let us create the other two view files.
Creating NonSecureMethod.cshtml View
Create a new view file named NonSecureMethod.cshtml within the Views/Home folder and then copy and paste the following code.
@{ ViewData["Title"] = "Public Access"; } <div class="container mt-1"> <div class="mx-auto" style="max-width: 800px;"> <div class="card shadow border-0 rounded"> <div class="card-body"> <h2 class="card-title mb-3 text-primary">Public Access</h2> <p class="card-text fs-5"> Welcome to the public section of the application. This area is accessible to all users without requiring authentication. </p> <p class="card-text"> Feel free to explore the available content. For additional features and personalized services, please consider creating an account or logging in. </p> <hr /> <p class="text-muted small mb-0"> To register or sign in, please use the links provided in the navigation menu above. </p> </div> </div> </div> </div>
Creating SecureMethod.cshtml View
Create a new view file named NonSecureMethod.cshtml within the Views/Home folder and then copy and paste the following code.
@{ ViewData["Title"] = "Secure Page"; } <div class="container mt-1"> <div class="mx-auto" style="max-width: 800px;"> <div class="card shadow border-0 rounded"> <div class="card-body"> <h2 class="card-title mb-3 text-primary">Access Authorized</h2> <p class="card-text fs-5"> You have successfully logged in and gained access to this secure area of the application. </p> <p class="card-text"> Please note that this section contains confidential information intended only for authorized users. To maintain security, do not share your login credentials or leave your session unattended. </p> <hr /> <p class="text-muted small mb-0"> For account management, password updates, or to log out safely, please use the options provided in the navigation menu. </p> </div> </div> </div> </div>
Link to all Action Views in Layout File:
Next, we need to add the link for all these action methods to be visible in the menus. So, open _Layout.cshtml file and then copy and paste the following code.
@{ ViewData["Title"] = ViewData["Title"] ?? "Dot Net Tutorials"; } <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>@ViewData["Title"] - Dot Net Tutorials</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet"> </head> <body class="d-flex flex-column min-vh-100"> <!-- Dark Navbar --> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <div class="container"> <a class="navbar-brand fw-bold" href="@Url.Action("Index", "Home")">Dot Net Tutorials</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent" aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarContent"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> <a class="nav-link" href="@Url.Action("Index", "Home")">Home</a> </li> <li class="nav-item"> <a class="nav-link" href="@Url.Action("About", "Home")">About</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-controller="Home" asp-action="SecureMethod">Secure</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-controller="Home" asp-action="NonSecureMethod">Non Secure</a> </li> </ul> <ul class="navbar-nav mb-2 mb-lg-0"> @if (User.Identity?.IsAuthenticated == true) { <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <i class="bi bi-person-circle me-1"></i> Hello, @User.Identity.Name </a> <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown"> <li> <a class="dropdown-item" href="@Url.Action("Profile", "Account")"> <i class="bi bi-person-lines-fill me-2"></i> Profile </a> </li> <li><hr class="dropdown-divider" /></li> <li> <form method="post" asp-controller="Account" asp-action="Logout" class="px-3 py-1"> <button type="submit" class="btn btn-link text-decoration-none w-100 text-start"> <i class="bi bi-box-arrow-right me-2"></i> Logout </button> </form> </li> </ul> </li> } else { <li class="nav-item"><a class="nav-link" href="@Url.Action("Login", "Account")">Login</a></li> <li class="nav-item"><a class="nav-link" href="@Url.Action("Register", "Account")">Register</a></li> } </ul> </div> </div> </nav> <!-- Main Content --> <main class="container flex-grow-1 py-3"> @RenderBody() </main> <!-- Footer --> <footer class="bg-dark text-light py-4 mt-auto"> <div class="container d-flex flex-column flex-md-row justify-content-between align-items-center"> <div> © @DateTime.Now.Year Dot Net Tutorials. All rights reserved. </div> <div> <a href="@Url.Action("Contact", "Home")" class="text-light me-3 text-decoration-none">Contact</a> <a href="@Url.Action("Privacy", "Home")" class="text-light me-3 text-decoration-none">Privacy Policy</a> <a href="@Url.Action("Terms", "Home")" class="text-light text-decoration-none">Terms of Service</a> </div> </div> </footer> <!-- Scripts --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> <script src="~/lib/jquery/dist/jquery.min.js"></script> @await RenderSectionAsync("Scripts", required: false) </body> </html>
How to Configure the Login URL for Redirecting Unauthenticated Users
When a user tries to access a page that requires authentication but isn’t logged in, ASP.NET Core Identity automatically redirects them to a login page. By default, this login page URL is /Account/Login. However, if your login page is at a different URL or if you want to specify the login path explicitly, you need to configure it.
Where to Configure?
This configuration happens in the Program.cs file, where our services and middleware are registered. We need to configure the login redirect URL through the cookie authentication middleware, specifically using ConfigureApplicationCookie. Here is how you do it:
builder.Services.ConfigureApplicationCookie(options => { // This sets the path to the login page users are redirected to if unauthenticated options.LoginPath = "/Account/Login"; // Change if your login page is elsewhere });
Here,
- If you don’t set LoginPath explicitly, ASP.NET Core assumes /Account/Login.
- So, if your login action is exactly at /Account/Login, you may omit this.
- But if your login page lives somewhere else (e.g., /User/Login), you must set this property correctly.
This setting ensures that unauthenticated users trying to access protected resources are sent to the proper login page.
How to Redirect to ReturnUrl after Login in ASP.NET Core?
When an unauthenticated user tries to access a protected page (say /Home/SecureMethod), the authentication middleware automatically redirects them to the login page, but appends a query parameter called ReturnUrl with the original page’s URL. For example:
/Account/Login?ReturnUrl=/Home/SecureMethod
This parameter is essential because, after the user logs in successfully, the app can send them back to the page they originally wanted to visit instead of just the homepage.
Note: The ASP.NET Core Framework is responsible for appending the original URL or redirect URL while navigating the user to the Login Page. We need to add a parameter to the Login action method to capture the query string parameter value. The parameter name in the Login action method to capture the query string value must be ReturnUrl.
Login Action Method (HttpGet):
So, please modify the HttpGet version of the Login action method of the Account Controller as follows. Here, we make the ReturnUrl parameter optional by initializing it with the null value. Next, store the ReturnUrl in the ViewData, which will serve as a hidden field in the login view, allowing us to post this value to the Login HTTP Post action method.
[HttpGet] public IActionResult Login(string? ReturnUrl = null) { ViewData["ReturnUrl"] = ReturnUrl; return View(); }
Modifying the Login Page:
In the Login page (in our example, Login.cshtml view), please ensure the login form includes the ReturnUrl as a hidden field. This will ensure that the ReturnUrl is sent back to the server when the user submits the form. So, modify the Login.cshtml file as follows:
@model ASPNETCoreIdentityDemo.ViewModels.LoginViewModel @{ ViewData["Title"] = "Login"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div class="container mt-3 mb-3"> <div class="row shadow-lg rounded-4 bg-white overflow-hidden mx-auto" style="max-width: 800px;"> <!-- Left: Login Section --> <div class="col-md-6 py-4 px-4 bg-light d-flex flex-column justify-content-center"> <h3 class="mb-2 text-center fw-bold text-primary">Sign in to <span class="text-dark">Dot Net Tutorials</span></h3> <p class="text-center text-muted mb-4 small"> <span class="fw-semibold text-secondary">Welcome back!</span> Please enter your credentials below to access your account. </p> <!-- Messages Section --> @if (ViewBag.Message != null) { <div class="alert alert-info alert-dismissible fade show mb-3 small text-center" role="alert"> @ViewBag.Message <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> } @if (!ViewData.ModelState.IsValid) { <div class="alert alert-danger alert-dismissible fade show small text-center" role="alert"> <strong>Please fix the errors below and try again:</strong> <ul class="mb-0 mt-2"> @foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors)) { <li>@error.ErrorMessage</li> } </ul> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> } <form asp-action="Login" method="post" novalidate> <input type="hidden" name="ReturnUrl" value="@ViewData["ReturnUrl"]" /> <div class="mb-3"> <label asp-for="Email" class="form-label fw-semibold text-primary"></label> <input asp-for="Email" class="form-control" placeholder="Enter your Email Address" autocomplete="username" /> <span asp-validation-for="Email" class="text-danger small"></span> </div> <div class="mb-3"> <label asp-for="Password" class="form-label fw-semibold text-primary"></label> <input asp-for="Password" class="form-control" type="password" placeholder="Enter Your Password" autocomplete="current-password" /> <span asp-validation-for="Password" class="text-danger small"></span> </div> <div class="form-check mb-3"> <input asp-for="RememberMe" class="form-check-input" /> <label asp-for="RememberMe" class="form-check-label small"></label> </div> <button type="submit" class="btn btn-primary w-100 fw-semibold py-2 mb-2">Sign In</button> <p class="mt-3 text-center mb-1"> <span class="text-secondary small">New to <span class="fw-semibold text-dark">Dot Net Tutorials</span>?</span> <a asp-action="Register" class="text-primary ms-1 text-decoration-none fw-semibold">Create an account</a> </p> <p class="text-center mb-0"> <a asp-action="ResendEmailConfirmation" class="text-info text-decoration-none small">Resend email confirmation</a> </p> </form> </div> <!-- Right: External Authentication --> <div class="col-md-6 py-4 px-4 d-flex flex-column justify-content-center align-items-center bg-white"> <div class="w-100" style="max-width: 280px;"> <h5 class="mb-3 fw-bold text-primary text-center">Quick sign-in</h5> <div class="mb-3 text-center text-muted small">Use your social account for instant access:</div> <div class="d-grid gap-3 mb-3"> <a href="#" class="btn btn-outline-primary d-flex align-items-center justify-content-center fw-semibold"> <img src="https://img.icons8.com/color/32/000000/google-logo.png" class="me-2" style="width:24px" alt="Google" /> Google </a> <a href="#" class="btn d-flex align-items-center justify-content-center fw-semibold" style="background-color: #3b5998; color: #fff;"> <img src="https://img.icons8.com/fluency/32/000000/facebook-new.png" class="me-2" style="width:24px" alt="Facebook" /> Facebook </a> <a href="#" class="btn btn-outline-dark d-flex align-items-center justify-content-center fw-semibold"> <img src="https://img.icons8.com/ios-filled/32/000000/github.png" class="me-2" style="width:24px" alt="GitHub" /> GitHub </a> <a href="#" class="btn d-flex align-items-center justify-content-center fw-semibold" style="background-color: #0078d4; color: #fff;"> <img src="https://img.icons8.com/color/32/000000/microsoft.png" class="me-2" style="width:24px" alt="Microsoft" /> Microsoft </a> </div> <div class="text-center text-muted small mt-4 mb-2"> <span>We never post anything without your permission.</span> </div> <hr class="mb-2" /> <div class="text-center text-muted small"> Need help? <a href="mailto:support@dotnettutorials.net" class="text-decoration-none fw-semibold">Contact Support</a> </div> </div> </div> </div> </div> @section Scripts { <partial name="_ValidationScriptsPartial" /> }
HttpPost Login Action Method:
Once the user submits their credentials, validate them. If validation is successful and a valid ReturnUrl is present, redirect the user to that URL. If no ReturnUrl is present, redirect the user to a default page, like the homepage or dashboard. So, please modify the HTTP Post version of the Login Action method of the Account Controller as follows:
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginViewModel model, string? returnUrl = null) { try { if (!ModelState.IsValid) return View(model); var result = await _accountService.LoginUserAsync(model); if (result.Succeeded) { // Redirect back to original page if ReturnUrl exists and is local if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) return Redirect(returnUrl); // Otherwise, redirect to a default page (like user profile) return RedirectToAction("Profile", "Account"); } // Handle login failure (e.g., invalid credentials or unconfirmed email) if (result.IsNotAllowed) ModelState.AddModelError("", "Email is not confirmed yet."); else ModelState.AddModelError("", "Invalid login attempt."); return View(model); } catch (Exception ex) { _logger.LogError(ex, "Error during login for email: {Email}", model.Email); ModelState.AddModelError("", "An unexpected error occurred. Please try again later."); return View(model); } }
Now, with the above changes in place, run the application, and it should work as expected.
What is the Use of the IsLocalUrl Method in ASP.NET Core?
The IsLocalUrl method determines whether the given URL is local. This method is useful when redirecting a user after an operation like login, as it helps prevent open redirect attacks. To test this, modify the hidden ReturnUrl field using the browser developer tool and set the value to a different website URL.
The method Url.IsLocalUrl checks if the URL you want to redirect to is local to your application (starts with / or ~ and does not contain a full domain).
An Open Redirect Attack occurs when an application redirects users to an external, potentially malicious URL. By using the IsLocalUrl method, we can ensure that our application only redirects to URLs within our application, thereby avoiding such security risks.
In the next article, I will discuss how to Implement Remote Validation in ASP.NET Core Identity. In this article, I try to explain how to redirect to ReturnUrl After Login in ASP.NET Core. I hope you enjoy this article.
The best tutorials on internet about ASP.NET Core Identity.
Saved as a favorite, I like your site!
Hello mates, good post and good arguments commented here, I am in fact enjoying by these.
Hurrah! At last I got a web site from where I be able to actually get
valuable facts concerning my study and knowledge.
nice