Dead Simple ASP.NET MVC Authentication

By in , , , ,
No comments

You may already know that, due to its lightweight architecture, ASP.NET MVC makes it simple to get a website up and running. But another advantage to such a manageable framework is how simple it is to implement any necessary component with just the bare minimum functionality. One such requirement is the need to enforce Forms Authentication for your website. When creating a simple prototype, you’re probably not concerned with the specifics of the user security, implementation, but you probably do want to demonstrate it! Several options are at your disposal, including the classic, full-fledged SQL Membership and newer Simple Membership options for MVC, and even the one that is generated by the ASP.NET MVC Visual Studio Template. However, sometimes when you’re in the extremely early stages of development (especially in an already existing project), all you want to do is slap a login on a page and make it work, without having to worry about database tables, connection strings, or any kind of external dependency. Just a dead-simple, hard-coded username and password will do. This is what we will accomplish with this post. AccountController and LoginViewModel The heart of this solution lies in creating a simple, minimalistic controller responsible for authenticating the user, along with a simple ViewModel to pass the credentials from the form:

public class LoginViewModel

    {

        [Required]

        public string Username { get; set; }

        [Required]

        public string Password { get; set; }

    }

Using a ViewModel also allows us to take advantage of the code generation offered by Visual Studio to automatically create a starting point for the form, which we’ll look at after we define the Controller. The only code we need for this controller is a method to show the form, then a method to accept a post (and LoginViewModel) from the form, matching against a hard-coded value, and authenticating if they match. Finally a Logout Action will clear out the cookie and log the user out. Below is the complete AccountController class:

public class AccountController : Controller

    {

        //

        // GET: /Account/

    public ActionResult Index()

    {

        return RedirectToAction("Login");

    }

        public ActionResult Login()

        {

            return View("Login");

    }

    [HttpPost]

    public ActionResult Login(LoginViewModel login)

    {

        if (!ModelState.IsValid)

        {

            ViewBag.Error = "Form is not valid; please review and try again.";

            return View("Login");

        }

        if (login.Username == "user" && login.Password == "password")

            FormsAuthentication.RedirectFromLoginPage(login.Username, true);

        ViewBag.Error = "Credentials invalid. Please try again.";

        return View("Login");

    }

    public ActionResult Logout()

    {

        Session.Clear();

        FormsAuthentication.SignOut();

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

    }

    }

  Now to generate the form, right-click the Login ActionResult and select “Add View”, which brings up a dialog allowing you to name the view, and strongly-type the view to the LoginViewModel, as well as the scaffold template. The “Create” option should suit our needs, as it will create an input field for both the Username and Password property. Once the view is generated, we simply need to change the second property’s EditorFor helper to a PasswordFor, as well as renaming the submit button to “Login” rather than “Create”. Below is the completed View for the Login.

@model NamesUp.Web.Models.LoginViewModel

@{

    ViewBag.Title = "Login";

}

<h2>Login</h2>

@if (ViewBag.Error != null) {

<p class="error">@Html.Raw(ViewBag.Error)</p>

}

@using (Html.BeginForm()) {

    @Html.AntiForgeryToken()

    @Html.ValidationSummary(true)

    <fieldset>

        <legend>Login</legend>

        <div class="editor-label">

            @Html.LabelFor(model => model.Username)

        </div>

        <div class="editor-field">

            @Html.EditorFor(model => model.Username)

            @Html.ValidationMessageFor(model => model.Username)

        </div>

        <div class="editor-label">

            @Html.LabelFor(model => model.Password)

        </div>

        <div class="editor-field">

            @Html.PasswordFor(model => model.Password)

            @Html.ValidationMessageFor(model => model.Password)

        </div>

        <p>

            <input type="submit" value="Login" />

        </p>

    </fieldset>

}

<div>

    @Html.ActionLink("Back to List", "Index")

</div>

@section Scripts {

    @Scripts.Render("~/bundles/jqueryval")

}

  LoginUrl and DefaultUrl Now when a user visits the Login page, they can post their credentials to the controller, which will validate against a specific set of credentials, redirecting them back to the desired page. If they were attempting to visit a controller-protected page (that uses the [Authorize] attribute on the class or action), they will receive a 401 error message. While technically correct, this is hardly useful for the user. Instead, you can define a LoginUrl property in the web.config file so that this exception automatically sends the user to that page. Likewise, if a user logs in successfully after attempting to visit a protected page, they will be automatically redirected via the auto-populated ReturlUrl querystring property. However, if they visit the login page directly, you probably want a way to redirect them to a default page (usually home). This too is a setting in web.config named DefaultUrl, and allows the RedirectFromLoginPage method of the FormsAuthentication object to send the user to the right place if they didn’t come from a protected page. To enable either or both of these features, take a look at this sample web.config file entry:

<authentication mode="Forms">

    <forms loginUrl="~/Account/Login" timeout="2880" defaultUrl="~/Home" />

</authentication>

Adding Authentication Links to Navigation Another helpful option you’ll probably want to create is a link for the user to access the login page if they are not authenticated, as well as a way to logout if they are. ASP.NET exposes a helpful User property, which can be inspected to determine the user status. Here we combine this property with a Telerik RadMenu MVC Helper on the shared layout template to list both options, toggling their view based on the user status.

<!DOCTYPE html>

<html>

<head>

    <title>@ViewBag.Title</title>

    @RenderSection("Scripts", false)

    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />

@(Html.Telerik().StyleSheetRegistrar().DefaultGroup(group => group.Add("telerik.common.css").Add("telerik.black.css").Combined(true).Compress(true)))</head>

<body>

    <div class="page">

        <header>

            <div id="title">

                <h1>My MVC Application</h1>

            </div>

            @(Html.Telerik().Menu()

                    .Name("menu")

                    .Items(menu => {

                        menu.Add().Text("Home").Action("Index", "Home");

                        menu.Add().Text("Login").Action("Login", "Account").Visible(!User.Identity.IsAuthenticated);

                        menu.Add().Text("Cards").Action("Index", "Items").Visible(User.Identity.IsAuthenticated);

                        menu.Add().Text("Logout").Action("Logout", "Account").Visible(User.Identity.IsAuthenticated);

                    }))

            <div style="clear:both;"></div>

        </header>

        <section id="main">

            @RenderBody()

        </section>

        <footer>

        </footer>

    </div>

@(Html.Telerik().ScriptRegistrar().DefaultGroup(group => group.Combined(true).Compress(true)))</body>

</html>

  Now if a user is anonymous, they will see the link to the login page. If they are authenticated, they’ll see the link to logout and clear their cookie. With all these pieces in place, you now have a dead simple authentication demo for your site, with no need to setup any other parts. Best of all when the time comes to add a concrete implementation, you simply need to update your AccountController with the appropriate logic. What could be simpler?

The following two tabs change content below.

selaromdotnet

Senior Developer at iD Tech
Josh loves all things Microsoft and Windows, and develops solutions for Web, Desktop and Mobile using the .NET Framework, Azure, UWP and everything else in the Microsoft Stack. His other passion is music, and in his spare time Josh spins and produces electronic music under the name DJ SelArom. His other passion is music, and in his spare time Josh spins and produces electronic music under the name DJ SelArom.