MVC 5 Without OWIN

MVC 5 Without OWIN

ASP.NET templates generate the code for many authentication options, such as registering new users, sending password resets, remembering passwords for subsequent logins, and supporting logins thru external social providers. In Visual Studio 2015 the default authentication selection is the "Identity" model which provides code and support structures for all these options. If you do not want all these features, you can remove the pieces you do not need. I wanted to use a very basic "Forms Authentication" so I removed many of the new Identity components to accomplish this. Below is the list of changes I made to an MVC 3.5.2 project using the "Identity" authentication model to get to a basic "Forms" type of authentication used at this site: Cathy Harris Website.

Removing OWIN Components and Setting up a Traditional "Forms" Authentication

The default MVC 5.2.3 project contains the newer Identity authentication model with the packages and hooks for OWIN. If you do not need social authentication, and you are going to host on IIS, the packages and hooks can be removed to reduce some of the bloat. The following is the procedure I used to do just that for the site: Cathy Harris Website.

  1. Uninstall NuGet Packages (All containing OWIN) in this order:
    1. Microsoft.Owin.Host.SystemWeb
    2. Microsoft.AspNet.Identity.Owin
    3. Microsoft.Owin.Security.OAuth
    4. Microsoft.Owin.Security.Facebook
    5. Microsoft.Owin.Security.MicrosoftAccount
    6. Microsoft.Owin.Security.Twitter
    7. Microsoft.Owin.Security.Google
    8. Microsoft.Owin.Security.Cookies
    9. Microsoft.Owin.Security
    10. Microsoft.Owin
    11. Owin

The remaining packages are show below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Antlr" version="3.5.0.2" targetFramework="net452" />
  <package id="bootstrap" version="3.3.5" targetFramework="net452" />
  <package id="EntityFramework" version="6.1.3" targetFramework="net452" />
  <package id="jQuery" version="2.1.4" targetFramework="net452" />
  <package id="jQuery.Validation" version="1.14.0" targetFramework="net452" />
  <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net452" />
  <package id="Microsoft.AspNet.Identity.EntityFramework" version="2.2.1" targetFramework="net452" />
  <package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net452" />
  <package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net452" />
  <package id="Microsoft.AspNet.Web.Optimization" version="1.1.3" targetFramework="net452" />
  <package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net452" />
  <package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.1" targetFramework="net452" />
  <package id="Microsoft.jQuery.Unobtrusive.Validation" version="3.2.3" targetFramework="net452" />
  <package id="Microsoft.Net.Compilers" version="1.0.0" targetFramework="net452" developmentDependency="true" />
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net452" />
  <package id="Modernizr" version="2.8.3" targetFramework="net452" />
  <package id="MySql.Data" version="6.9.7" targetFramework="net452" />
  <package id="MySql.Data.Entity" version="6.9.7" targetFramework="net452" />
  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" />
  <package id="Respond" version="1.4.2" targetFramework="net452" />
  <package id="WebGrease" version="1.6.0" targetFramework="net452" />
</packages>


packages.config After all OWIN Packages removed


  1. Removing Unneeded Configurations.
    1. App_Start Folder
      1. Delete IdentityConfig.cs
      2. Delete StartupAuth.cs
      3. No Change to BundleConfig.cs
      4. No Change to FilterConfig.cs
      5. No Change to RouteConfig.cs
    2. Content Folder
      1. No Change
    3. Web.config
      1. Remove any bindingRedirect's in the assemblyBinding section which reference OWIN, such as those shown below.

         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        			<dependentAssembly>
        				<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31BF3856AD364E35" culture="neutral" />
        				<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
        			</dependentAssembly>
        			<dependentAssembly>
        				<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31BF3856AD364E35" culture="neutral" />
        				<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
        			</dependentAssembly>
        			<dependentAssembly>
        				<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31BF3856AD364E35" culture="neutral" />
        				<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
        			</dependentAssembly>
        			<dependentAssembly>
        				<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31BF3856AD364E35" culture="neutral" />
        				<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
        			</dependentAssembly>
        


        bindingRedirects that Reference OWIN

  1. Setting up the Authentication Data.
    1. Set up the database tables to authenticate the users (example below.)
    2. Create the method to authenticate users (example below).
.Database Structure to Support Authentication


Database Structure to Support Authentication

I used the database structure show above as my data source for authentication. Users are assigned to Roles which are given Permission Levels that control the type of access they have to the website. This allows individuals or groups to have increased or decreased permission levels as the occasion requires. The "Admin" role is typically assigned to the highest level of permissions which has access to all parts and functions of the website. A "suspended" role exists which provide no access to the website.the website in case you need to temporarily block a user from the website. The authentication code from "AccountController.cs" is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
        //
        // POST: /Account/Login
        // POST Login data to database
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Login(LoginViewModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {

                using (var context = new CathySiteEntities())
                {
                    var userList = context.users
                        .Where(u => u.User_Name == model.UserName
                            && u.Password == model.Password
                            && u.role.permission.Level > 0)
                        .ToList();
                    if (userList.Count() == 1)
                    {
                        FormsAuthentication.SetAuthCookie(model.UserName, false);
                        return RedirectToAction("Index", "Admin");
                    }
                    else
                    {
                        ModelState.AddModelError("", "The User Name or Password is incorrect.");
                    }
                }
            }
            return View(model);
        }


Authentication Code in AccountController.cs

Note the last condition in the lambda expression of the where clause is: u.role.permission.Level > 0. Since the foreign keys were set up in the database, Entity Framework created the Navigation Properties which allow this short syntax for checking the permission level of the user. The equivalent SQL is:

  1. Remove Startup.cs
    1. Startup.cs calls ConfigureAuth which was declared in Startup.Auth.cs. Startup.Auth.cs was deleted in a previous step.
  1. Disable OWIN startup discovery.
    1. If may be necessary to disable OWIN start up discovery. This is done by add the appSetting owin:AutomaticAppStartup with a value of "false" in your web.config, as shown below.

      1
      2
      3
      4
      5
      6
      7
        <appSettings>
          <add key="webpages:Version" value="3.0.0.0" />
          <add key="webpages:Enabled" value="false" />
          <add key="ClientValidationEnabled" value="true" />
          <add key="UnobtrusiveJavaScriptEnabled" value="true" />
          <add key="owin:AutomaticAppStartup" value="false" />
        </appSettings>
      


      Disable OWIN Start Up Discovery

.Controllers
  1. The default MVC 5.2.3 project contains 3 controllers. One controller is not needed and the other two are modified.
    1. ManagerController.cs - Delete as it is not needed.
    2. AccountController.cs - Modify as shown below.
    3. HomeController.cs - Modify as shown below
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using System.Web.Mvc;
using System.Linq;
using System.Web.Security;
using CathySite.DAL.Models;
using CathySite.Web.Models;

namespace CathySite.Web.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        //
        // GET: /Account/Login
        // Get Login data to display.
        [AllowAnonymous]
        public ActionResult Login(string returnUrl)
        {
            ViewBag.ReturnUrl = returnUrl;
            return View();
        }

        //
        // POST: /Account/Login
        // POST Login data to database
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Login(LoginViewModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {
                using (var context = new CathySiteEntities())
                {
                    int UserID = context.users
                        .Where(u => u.User_Name == model.UserName
                            && u.Password == model.Password
                            && u.role.permission.Level > 0)
                        .Select(u => u.User_ID)
                        .FirstOrDefault();
                    string sql = UserID.ToString();

                    if (UserID > 0)
                    {
                        FormsAuthentication.SetAuthCookie(model.UserName, false);
                        return RedirectToAction("Index", "Admin");
                    }
                    else
                    {
                        ModelState.AddModelError("", "The User Name or Password is incorrect.");
                    }
                }
            }
            return View(model);
        }

        //
        // POST: /Account/LogOff
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();
            return RedirectToAction("Login", "Account");
        }
    }
}


Modified AccountController.cs


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using System.Web.Mvc;
using System.Linq;
using CathySite.DAL.Models;

namespace CathySite.Web.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult About()
        {            
            using (var context = new CathySiteEntities())
            {
                ViewBag.Page = context.pages
                .Where(p => p.Page_Name == "/home/about")
                .FirstOrDefault();
            }
            return View();
        }

        public ActionResult Contact()
        {
            using (var context = new CathySiteEntities())
            {
                ViewBag.Page = context.pages
                .Where(p => p.Page_Name == "/home/contact")                
                .FirstOrDefault();
            }

            return View();
        }
    }
}


Modified HomeController.cs

.Models
  1. The default MVC 5.2.3 project contains 3 models. Two models are not needed and the other one is modified.
    1. IdentityModels.cs - Delete as it is not needed.
    2. ManageViewModels.cs - Delete as it is not needed.
    3. AccountViewModel.cs - Modify as shown below
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
using System.ComponentModel.DataAnnotations;

namespace CathySite.Web.Models
{
    public class LoginViewModel
    {
        [Required(ErrorMessage = "User Name is required.")]
        [Display(Name = "User Name")]
        public string UserName { get; set; }

        [Required(ErrorMessage = "Password is required.")]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }
    }
}


AccountViewModel Modified


.Views
  1. The default MVC 5.2.3 project contains Razor views in 4 folders. The views in the Shared folder are left as it is. The Manage folder and all its views are deleted. The views in the Account and Home folders are modified.
    1. Shared - Leave the views in this folder as-is.
    2. Manage - Delete this folder and all of its views.
    3. Account - Modify the views in this folder as shown below.
    4. Home - Modify the views in this folder as shown below,
.Remaining Views


Remaining Views


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@using CathySite.Web.Models;
@model LoginViewModel

<hgroup class="title">
    <h1 class="centerText">Website Administration</h1>
</hgroup>

<section id="loginForm">
    <h5>Please Log In</h5>
    @using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl }))
    {
        @Html.AntiForgeryToken()
        @Html.ValidationSummary(true)
        <div style="padding-top: 15px;">
            <table cellpadding= "10">
                <tr>
                    <td>
                        @Html.LabelFor(m => m.UserName)
                    </td>
                    <td>
                        @Html.TextBoxFor(m => m.UserName)
                    </td>
                    <td>
                        @Html.ValidationMessageFor(m => m.UserName)
                    </td>
                </tr>
                <tr>
                    <td>
                        @Html.LabelFor(m => m.Password)
                    </td>
                    <td>
                        @Html.PasswordFor(m => m.Password)
                    </td>
                    <td>
                        @Html.ValidationMessageFor(m => m.Password)
                    </td>
                </tr>
                <tr>
                    <td></td>
                    <td>
                        <input type="submit" value="Log in" />
                    </td>
                    <td></td>
                </tr>
            </table>
        </div>
    }
</section>
<section>
    <div style="width:90%;">
        <h3>Administration Area</h3>
        <h5>
            This is a restricted area for maintaining the contents of this website. The restricted area includes a home-grown content management system for changing the contents of some
            of the web pages without the need to modify any programs.
        </h5>
    </div>
</section>


Login.cshtml


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@{
    ViewBag.Title = "About";
}
<h2>@ViewBag.Title.</h2>

@Html.Raw(ViewBag.Page.Text1)

<p>
    Components making up this website:
    <ul>
        <li>.NET 4.5.2 (build level)</li>
        <li>MVC 5.2.3</li>
        <li>Entity Framework 6.1.3</li>
        <li>MySQL 5.1</li>
        <li>Bootstrap 3.0.0</li>
        <li>jQuery 1.10.2</li>
        <li>Modern2nizr 2.6.2</li>
        <li>WebGrease 1.5.2</li>
        <li>CKEditor 3.6.4</li>
    </ul>
</p>

<p>
    Tools used in the making this website:
    <ul>
        <li>Visual Studio 2015 Community (v14.0.23)</li>
        <li>AnkhSVN Subversion Plugin 2.5.12703</li>
        <li>MySQL Workbench 6.3.3 Community</li>
        <li>VisualSVN 3.4.2</li>
    </ul>
</p>

<p>
    Design of this website:
    <ul>
        <li>Languages used: C#, JavaScript, with a lot of jQuery.</li>
        <li>All database access is thru LINQ to Entities.</li>
        <li>The contents of many of the public pages are pulled from a database. The private section of the website contains a home grown content management system.</li>
    </ul>
</p>


About.cshtml


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@{
    ViewBag.Title = "Contact";
}
<h2>@ViewBag.Title.</h2>

@Html.Raw(ViewBag.Page.Text1)

<div style="text-align:center">
    <img src="@Url.Content("~/Content/Images/suzyhl2.png")" alt="Collage" />
</div>


Contact.cshtml


1
2
3
4
5
6
@{
    ViewBag.Title = "Home Page";
}
<div style="width:90%;">
    <img src="@Url.Content("~/Content/Images/collage1.png")" alt="Collage" />
</div>


Index.cshtml




Error | ASP.NET Developer

Error

Error message

  • Warning: Cannot modify header information - headers already sent by (output started at /srv/disk9/1218369/www/kcshadow.net/aspnet/includes/common.inc:2748) in drupal_send_headers() (line 1232 of /srv/disk9/1218369/www/kcshadow.net/aspnet/includes/bootstrap.inc).
  • PDOException: SQLSTATE[42000]: Syntax error or access violation: 1142 INSERT command denied to user '1218369_b2cf'@'185.176.40.58' for table 'watchdog': INSERT INTO {watchdog} (uid, type, message, variables, severity, link, location, referer, hostname, timestamp) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6, :db_insert_placeholder_7, :db_insert_placeholder_8, :db_insert_placeholder_9); Array ( [:db_insert_placeholder_0] => 0 [:db_insert_placeholder_1] => cron [:db_insert_placeholder_2] => Attempting to re-run cron while it is already running. [:db_insert_placeholder_3] => a:0:{} [:db_insert_placeholder_4] => 4 [:db_insert_placeholder_5] => [:db_insert_placeholder_6] => http://www.kcshadow.net/aspnet/?q=mvcauthentication [:db_insert_placeholder_7] => [:db_insert_placeholder_8] => 54.80.87.62 [:db_insert_placeholder_9] => 1534851875 ) in dblog_watchdog() (line 160 of /srv/disk9/1218369/www/kcshadow.net/aspnet/modules/dblog/dblog.module).
The website encountered an unexpected error. Please try again later.