Hinweis: Dieser Artikel wurde ursprünglich veröffentlicht in 2009 for ASP.NET Web Forms. It has been expanded to cover ASP.NET MVC, ASP.NET Core, and modern authentication patterns. The fundamental concepts apply across all ASP.NET frameworks.

Redirecting unauthenticated users to a login page is one of the most fundamental requirements of any web application. In ASP.NET, there are several ways to accomplish this, ranging from manual redirects to framework-integrated approaches that read configuration from web.config and automatically preserve the user’s intended destination. Choosing the right approach affects maintainability, security, and user experience.

The Problem

You have a web application where certain pages require authentication. When an unauthenticated user requests a protected page, you need to:

  1. Redirect them to the login page.
  2. After successful login, send them back to the page they originally requested.
  3. Keep the login page URL configured in a single place for maintainability.

The most integrated method in classic ASP.NET is FormsAuthentication.RedirectToLoginPage(). This method reads the login page URL from your web.config configuration, so you only define it once.

Web.config Konfiguration

<configuration>
  <system.web>
    <authentication mode="Forms">
      <forms loginUrl="~/Account/Login.aspx"
             timeout="30"
             slidingExpiration="true"
             cookieless="UseCookies"
             protection="All" />
    </authentication>
    <authorization>
      <deny users="?" />  <!-- Deny anonymous users -->
    </authorization>
  </system.web>
</configuration>

Verwendung in Code-Behind

using System.Web.Security;

/// <summary>
/// Check if the current user is authenticated; if not, redirect to the login page.
/// </summary>
private void CheckUserIsAuthenticated()
{
    MembershipUser user = Membership.GetUser();
    if (user == null || string.IsNullOrEmpty(user.UserName))
    {
        FormsAuthentication.RedirectToLoginPage();
        return; // Important: code after RedirectToLoginPage WILL execute
    }
}

Critical Behavior: Thread Continues Executing

Unlike Response.Redirect(), which throws a ThreadAbortException to terminate the current request, FormsAuthentication.RedirectToLoginPage() does not stop the executing thread. This means any code after the call will still run:

// WRONG: Code after redirect will execute
private void LoadSensitiveData()
{
    if (!User.Identity.IsAuthenticated)
    {
        FormsAuthentication.RedirectToLoginPage();
        // This code WILL run even though the redirect was issued!
        var data = LoadConfidentialRecords();
        DisplayData(data);
    }
}

// CORRECT: Add a return statement after the redirect
private void LoadSensitiveData()
{
    if (!User.Identity.IsAuthenticated)
    {
        FormsAuthentication.RedirectToLoginPage();
        return; // Prevents further code execution
    }

    var data = LoadConfidentialRecords();
    DisplayData(data);
}

How ReturnUrl Works

When FormsAuthentication.RedirectToLoginPage() is called, it automatically appends the current URL as a ReturnUrl query parameter:

/Account/Login.aspx?ReturnUrl=%2fAdmin%2fDashboard.aspx

After successful authentication, you can redirect the user back using:

// In your login page's authentication logic
if (Membership.ValidateUser(username, password))
{
    FormsAuthentication.RedirectFromLoginPage(username, createPersistentCookie: false);
    // This reads ReturnUrl and redirects the user back to their original page
}

Approach 2: Response.Redirect (Manual Approach)

If you need more control or are not using Forms Authentication, you can use Response.Redirect directly:

// Basic redirect
Response.Redirect("~/Account/Login.aspx");

// Redirect with ReturnUrl manually
string returnUrl = HttpUtility.UrlEncode(Request.RawUrl);
Response.Redirect($"~/Account/Login.aspx?ReturnUrl={returnUrl}");

Ending the Response

Response.Redirect has an overload that controls whether execution stops:

// Throws ThreadAbortException and stops execution (default behavior)
Response.Redirect("~/Account/Login.aspx", true);

// Does NOT throw ThreadAbortException; code continues (like RedirectToLoginPage)
Response.Redirect("~/Account/Login.aspx", false);

The endResponse: true option is the default and ensures no code runs after the redirect, but it has a performance cost due to the exception. Using false is more efficient but requires you to manage flow control manually.

Sicherheit Consideration: Open Redirect Vulnerability

When implementing ReturnUrl manually, always validate the URL to prevent open redirect attacks:

// UNSAFE: Attacker can craft a URL like ?ReturnUrl=https://evil.com
string returnUrl = Request.QueryString["ReturnUrl"];
Response.Redirect(returnUrl); // Never do this!

// SAFE: Validate that the URL is local
private void SafeRedirectAfterLogin(string returnUrl)
{
    if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
    {
        Response.Redirect(returnUrl);
    }
    else
    {
        Response.Redirect("~/Default.aspx");
    }
}

Approach 3: The [Authorize] Attribute (ASP.NET MVC)

In ASP.NET MVC, the [Authorize] attribute is the standard way to protect controllers and actions. When an unauthenticated user hits a protected action, the framework automatically redirects them to the login page.

Controller-Level Authorization

using System.Web.Mvc;

// Protect the entire controller
[Authorize]
public class AdminController : Controller
{
    public ActionResult Dashboard()
    {
        return View();
    }

    public ActionResult Settings()
    {
        return View();
    }
}

Action-Level Authorization

public class AccountController : Controller
{
    // Public: anyone can access
    [AllowAnonymous]
    public ActionResult Login()
    {
        return View();
    }

    // Protected: only authenticated users
    [Authorize]
    public ActionResult Profile()
    {
        return View();
    }

    // Protected: only users in the "Admin" role
    [Authorize(Roles = "Admin")]
    public ActionResult ManageUsers()
    {
        return View();
    }
}

How It Works Under the Hood

When [Authorize] denies access:

  1. The AuthorizeAttribute filter sets the HTTP response status to 401 Unauthorized.
  2. The Forms Authentication module intercepts the 401 response.
  3. It redirects the browser to the loginUrl configured in web.config.
  4. It appends the ReturnUrl query parameter automatically.

Login Action with ReturnUrl Handling

[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    if (Membership.ValidateUser(model.Username, model.Password))
    {
        FormsAuthentication.SetAuthCookie(model.Username, model.RememberMe);

        // Safe redirect back to the original page
        if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1
            && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//")
            && !returnUrl.StartsWith("/\\"))
        {
            return Redirect(returnUrl);
        }

        return RedirectToAction("Index", "Home");
    }

    ModelState.AddModelError("", "Invalid username or password.");
    return View(model);
}

Approach 4: ASP.NET Core Authentication Middleware

ASP.NET Core replaces Forms Authentication with a middleware-based pipeline. The cookie authentication middleware handles login redirects automatically.

Configuring Authentication in Program.cs

// Program.cs (ASP.NET Core 6+ minimal hosting)
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.LoginPath = "/Account/Login";
        options.LogoutPath = "/Account/Logout";
        options.AccessDeniedPath = "/Account/AccessDenied";
        options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
        options.SlidingExpiration = true;
        options.Cookie.HttpOnly = true;
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        options.Cookie.SameSite = SameSiteMode.Lax;
    });

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();

Protecting Controllers and Actions

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Authorize]
public class DashboardController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

Login and Logout in ASP.NET Core

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using System.Security.Claims;

public class AccountController : Controller
{
    [AllowAnonymous]
    [HttpGet]
    public IActionResult Login(string returnUrl = null)
    {
        ViewData["ReturnUrl"] = returnUrl;
        return View();
    }

    [AllowAnonymous]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
    {
        if (!ModelState.IsValid)
            return View(model);

        // Validate credentials (replace with your own logic)
        if (IsValidUser(model.Username, model.Password))
        {
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, model.Username),
                new Claim(ClaimTypes.Role, "User"),
            };

            var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
            var principal = new ClaimsPrincipal(identity);

            await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                principal,
                new AuthenticationProperties
                {
                    IsPersistent = model.RememberMe,
                    ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(30)
                });

            // Safe redirect
            if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
                return Redirect(returnUrl);

            return RedirectToAction("Index", "Home");
        }

        ModelState.AddModelError("", "Invalid credentials.");
        return View(model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Logout()
    {
        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        return RedirectToAction("Login");
    }
}

Policy-Based Authorization in ASP.NET Core

ASP.NET Core also supports policy-based authorization for more granular control:

// In Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
    options.AddPolicy("MinimumAge", policy => policy.RequireClaim("Age", "18"));
});

// On a controller or action
[Authorize(Policy = "AdminOnly")]
public IActionResult AdminPanel()
{
    return View();
}

Approach 5: Global Filters and Middleware

ASP.NET MVC Global Filter

To require authentication globally without adding [Authorize] to every controller:

// In FilterConfig.cs or Global.asax
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new AuthorizeAttribute());
}

Then use [AllowAnonymous] on specific actions that should be publicly accessible (login page, registration, etc.).

ASP.NET Core Fallback Policy

In ASP.NET Core, you can set a fallback authorization policy that requires authentication by default:

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

With this configuration, every endpoint requires authentication unless explicitly marked with [AllowAnonymous].

Comparison of Approaches

ApproachFrameworkCentralized ConfigAuto ReturnUrlStops Execution
FormsAuthentication.RedirectToLoginPage()ASP.NET Web FormsYes (web.config)YesNo
Response.Redirect()Any ASP.NETNo (hardcoded)ManualConfigurable
[Authorize] attributeASP.NET MVCYes (web.config)YesYes (401 pipeline)
Cookie Auth MiddlewareASP.NET CoreYes (Program.cs)YesYes (middleware pipeline)
Global Filter / Fallback PolicyMVC / CoreYesYesYes

Bewährte Methoden

  1. Never hardcode the login URL in multiple places. Use FormsAuthentication.RedirectToLoginPage() (Web Forms), the [Authorize] attribute (MVC), or the cookie authentication middleware (Core) to centralize it.
  2. Always validate ReturnUrl against open redirect attacks using Url.IsLocalUrl().
  3. Add a return statement after FormsAuthentication.RedirectToLoginPage() since it does not terminate the thread.
  4. Use HTTPS for all authentication-related pages to prevent credential interception.
  5. Set the HttpOnly and Secure flags on authentication cookies to prevent XSS-based cookie theft and transmission over HTTP.
  6. Prefer [Authorize] attributes over manual authentication checks in code. Declarative security is easier to audit and less error-prone.
  7. Use [AllowAnonymous] explicitly on endpoints that should be public when using global authorization filters.

Zusammenfassung

The best way to redirect to a login page in ASP.NET depends on your framework version. For classic ASP.NET Web Forms, FormsAuthentication.RedirectToLoginPage() provides centralized configuration and automatic ReturnUrl handling. For ASP.NET MVC, the [Authorize] attribute integrates with Forms Authentication to handle redirects declaratively. For ASP.NET Core, the cookie authentication middleware in combination with [Authorize] offers the most modern and flexible approach. In all cases, validate return URLs, use HTTPS, and ensure the redirect does not allow subsequent code to execute with unauthenticated context.

Verwandte Artikel