Forms Authentication (B), Using Database

These will be a series articles about security issue of ASP.NET Authentication:

Introduction

 
After I wrote the first security article, Authentication (1): Windows Authentication, I originally planned to write other two articles to summarize Forms Authentication and Passport Authentication, then concentrate on the new, cutting edge security OAuth and OpenID Authentication. However, I found out the staff in, such as, Forms Authentication is quite huge, it is very hard to summarize this topic within one article (I do not want to just make words summary, but with sample for support), then I think I'd better to split one article into severals with each article only has one topic.  That must be fit to the SOLID principle, especially for principle one and two:
  • The Single-responsibility principle: Every class should have only one responsibility.
  • The Open–closed principle: Software entities ... should be open for extension, but closed for modification.
This way will make me easier to write the articles, and also easier for readers to read.
 

B: Forms Authentication using Own Database

 
This example will demonstrate the Forms Authentication using own Database:
 
Step 0: Create a Database
  1. -- Creating a database  
  2. CREATE DATABASE MVC_DB  
  3. GO  
  4. USE MVC_DB  
  5. GO  
  6. -- Creating Users table  
  7. CREATE TABLE Users  
  8. (  
  9.  ID INT PRIMARY KEY IDENTITY(1,1),  
  10.  UserName VARCHAR(50),  
  11.  UserPassword VARCHAR(50)  

Step 1: Create an ASP.NET MVC App
 
We use the current version of Visual Studio 2019 16.9.4 and .NET Framework 4.8 to build the app.
  • Start Visual Studio and select Create a new project.
  • In the Create a new project dialog, select ASP.NET Web Application (.NET Framework) > Next.
  • In the Configure your new project Dialog Box, enter SecurityDemoMVC for Project name
  • In the Create a New ASP.NET Web Application Dialog Box, select MVC > Create
Step 2: Setup Entity and Database Connection
 
1, Add Entity Class
  • Right Click project/Models > Add > Class
  • In the opened Box: specify the item name > OK
User.cs
  1. namespace SecurityDemoMVC.Models  
  2. {  
  3.     public class User  
  4.     {  
  5.         public int ID { getset; }  
  6.         public string UserName { getset; }  
  7.         public string UserPassword { getset; }  
  8.     }  

2,  Add Context Class
 
UserDBContext.cs
  1. using System.Data.Entity;  
  2.   
  3. namespace SecurityDemoMVC.Models  
  4. {  
  5.     public partial class UserDBContext : DbContext  
  6.     {  
  7.         public UserDBContext()  
  8.             : base("name=UserDBContext")  
  9.         {  
  10.         }  
  11.         public virtual DbSet<User> Users { getset; }  
  12.     }  

For the namespace: System.Data.Entity, you have to install EntityFramework,
  • Click "Tools=>NuGet Package Manager=>Manage NuGet Packages for Solution"
  • Browse=>Type EntityFramework,
  • Install

3,  Add Connection String
 
in Web.Config, under <configuration>
  1. <connectionStrings>  
  2.   <add name="UserDBContext" connectionString="Data Source=.; Initial Catalog=MVC_DB; Integrated Security=SSPI" providerName="System.Data.SqlClient" />  
  3. </connectionStrings> 
Step 3: Setup Login, Log Out
 
1, Add Controller
 
Controllers/AccountsController.cs
  1. using SecurityDemoMVC.Models;  
  2. using System.Linq;  
  3. using System.Web.Mvc;  
  4. using System.Web.Security;  
  5.   
  6. namespace SecurityDemoMVC.Controllers  
  7. {  
  8.     public class AccountsController : Controller  
  9.     {  
  10.         // GET: Accounts  
  11.         public ActionResult Login()  
  12.         {  
  13.             return View();  
  14.         }  
  15.   
  16.         [HttpPost]  
  17.         public ActionResult Login(User model, string ReturnUrl)  
  18.         {  
  19.             using (UserDBContext context = new UserDBContext())  
  20.             {  
  21.                 bool IsValidUser = context.Users.Any(user => user.UserName.ToLower() ==  
  22.                      model.UserName.ToLower() && user.UserPassword == model.UserPassword);  
  23.   
  24.                 if (IsValidUser)  
  25.                 {  
  26.                     FormsAuthentication.SetAuthCookie(model.UserName, false);  
  27.   
  28.                      if (ReturnUrl != null)  
  29.                     {   
  30.                         var decodedUrl = Server.UrlDecode(ReturnUrl);  
  31.                         return Redirect(decodedUrl);  
  32.                     }  
  33.                     else  
  34.                         return RedirectToAction("Index""Home");  
  35.                 }  
  36.   
  37.                 ModelState.AddModelError("""invalid Username or Password");  
  38.                 return View();  
  39.             }  
  40.         }  
  41.         public ActionResult Signup()  
  42.         {  
  43.             return View();  
  44.         }  
  45.   
  46.         [HttpPost]  
  47.         public ActionResult Signup(User model)  
  48.         {  
  49.             using (UserDBContext context = new UserDBContext())  
  50.             {  
  51.                 context.Users.Add(model);  
  52.                 context.SaveChanges();  
  53.             }   
  54.             return RedirectToAction("Login");  
  55.         }  
  56.         public ActionResult Logout()  
  57.         {  
  58.             FormsAuthentication.SignOut();  
  59.             return RedirectToAction("Login");  
  60.         }  
  61.     }  

here, the authentication job is done by database:
  • bool IsValidUser = context.Users.Any(user => user.UserName.ToLower() == model.UserName.ToLower() && user.UserPassword == model.UserPassword);
while the authorization job is done by FormsAuthentication Class:
  • FormsAuthentication.SetAuthCookie(model.UserName, false);
  • FormsAuthentication.SignOut();
where FormsAuthentication.SetAuthCookie(model.UserName, false) Method Creates an authentication ticket for the supplied user name and adds it to the cookies collection of the response, or to the URL if you are using cookieless authentication[ref].
 
2, Add Login, Signup Views
 
Views/Accounts/Login.cshtml
  1. @model SecurityDemoMVC.Models.User  
  2.   
  3. @{  
  4.     ViewBag.Title = "Login";  
  5. }  
  6.   
  7. <h2>Login</h2>  
  8.   
  9. @using (Html.BeginForm())  
  10. {  
  11.     @Html.AntiForgeryToken()  
  12.   
  13.     <div class="form-horizontal">  
  14.         <hr />  
  15.         @Html.ValidationSummary(true""new { @class = "text-danger" })  
  16.         <div class="form-group">  
  17.             @Html.LabelFor(model => model.UserName, htmlAttributes: new { @class = "control-label col-md-2" })  
  18.             <div class="col-md-10">  
  19.                 @Html.TextBoxFor(model => model.UserName, new { htmlAttributes = new { @class = "form-control" } })  
  20.                 @Html.ValidationMessageFor(model => model.UserName, ""new { @class = "text-danger" })  
  21.             </div>  
  22.         </div>  
  23.   
  24.         <div class="form-group">  
  25.             @Html.Label("Password", htmlAttributes: new { @class = "control-label col-md-2" })  
  26.             <div class="col-md-10">  
  27.                 @Html.TextBoxFor(model => model.UserPassword, new { htmlAttributes = new { @class = "form-control" } })  
  28.                 @Html.ValidationMessageFor(model => model.UserPassword, ""new { @class = "text-danger" })  
  29.             </div>  
  30.         </div>  
  31.   
  32.         <div class="form-group">  
  33.             <div class="col-md-offset-2 col-md-10">  
  34.                 <input type="submit" value="Login" class="btn btn-default" />  
  35.             </div>  
  36.         </div>  
  37.     </div>  
  38. }  
  39.   
  40. <div>  
  41.     @Html.ActionLink("Click here to Signup""Signup")  
  42. </div> 
Views/Accounts/SignUp.cshtml
  1. @model SecurityDemoMVC.Models.User  
  2.   
  3. @{  
  4.     ViewBag.Title = "Signup";  
  5. }  
  6.   
  7. <h2>Signup</h2>  
  8.   
  9. @using (Html.BeginForm())  
  10. {  
  11.     @Html.AntiForgeryToken()  
  12.   
  13.     <div class="form-horizontal">  
  14.         <hr />  
  15.         @Html.ValidationSummary(true""new { @class = "text-danger" })  
  16.         <div class="form-group">  
  17.             @Html.LabelFor(model => model.UserName, htmlAttributes: new { @class = "control-label col-md-2" })  
  18.             <div class="col-md-10">  
  19.                 @Html.TextBoxFor(model => model.UserName, new { htmlAttributes = new { @class = "form-control" } })  
  20.                 @Html.ValidationMessageFor(model => model.UserName, ""new { @class = "text-danger" })  
  21.             </div>  
  22.         </div>  
  23.   
  24.         <div class="form-group">  
  25.             @Html.Label("Password", htmlAttributes: new { @class = "control-label col-md-2" })  
  26.             <div class="col-md-10">  
  27.                 @Html.PasswordFor(model => model.UserPassword, new { htmlAttributes = new { @class = "form-control" } })  
  28.                 @Html.ValidationMessageFor(model => model.UserPassword, ""new { @class = "text-danger" })  
  29.             </div>  
  30.         </div>  
  31.   
  32.         <div class="form-group">  
  33.             <div class="col-md-offset-2 col-md-10">  
  34.                 <input type="submit" value="Signup" class="btn btn-default" />  
  35.             </div>  
  36.         </div>  
  37.     </div>  
  38. }  
  39.   
  40. <div>  
  41.     @Html.ActionLink("Click here to Login""Login")  
  42. </div> 
3, Add Layout page
 
For convenience, we add layout page:
 
Views/Shared/_LoginPartial.cshtml
  1. @if (Request.IsAuthenticated)  
  2. {  
  3.     using (Html.BeginForm("LogOff""Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))  
  4.     {  
  5.         @Html.AntiForgeryToken()  
  6.         <ul class="nav navbar-nav navbar-right">  
  7.             <li><a href="#">Welcome : @Session["UserName"]</a></li>  
  8.             @*<li><a href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li>*@  
  9.             <li>@Html.ActionLink("Log out""Logout""Accounts", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>  
  10.         </ul>  
  11.     }  
  12. }  
  13. else  
  14. {  
  15.     <ul class="nav navbar-nav navbar-right">  
  16.         <li>@Html.ActionLink("Log in""Login""Accounts", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>  
  17.     </ul>  
And we need to add one line in Views/Shared/_Layout.cshtml
  1. <body>  
  2.     <div class="navbar navbar-inverse navbar-fixed-top">  
  3.         <div class="container">  
  4.             <div class="navbar-header">  
  5.                 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">  
  6.                     <span class="icon-bar"></span>  
  7.                     <span class="icon-bar"></span>  
  8.                     <span class="icon-bar"></span>  
  9.                 </button>  
  10.                 @Html.ActionLink("Application name""Index""Home"new { area = "" }, new { @class = "navbar-brand" })  
  11.             </div>  
  12.             <div class="navbar-collapse collapse">  
  13.                 <ul class="nav navbar-nav">  
  14.                     <li>@Html.ActionLink("Home""Index""Home")</li>  
  15.                     <li>@Html.ActionLink("About""About""Home")</li>  
  16.                     <li>@Html.ActionLink("Contact""Contact""Home")</li>  
  17.                 </ul>  
  18.                 @Html.Partial("_LoginPartial" 
  19.             </div>  
  20.         </div>  
  21.     </div> 
  22.     ......
  23. </body>
Run the App, the Login/Logout/Signin operations will work,
 
 
Step 4: Configure Forms Authentication
 
1, Configure Authentication Mode
 
In web.config file:
  1. <system.web>  
  2.   <compilation debug="true" targetFramework="4.8" />  
  3.   <httpRuntime targetFramework="4.8" />  
  4.   
  5.  <authentication mode="Forms" 
  6.   <forms loginUrl="Accounts/Login"></forms>  
  7.  </authentication>  
  8. </system.web> 
2, Set Authorize Filter
 
We can make authorization filter as what Web App does (see Forms Authentication (A), using XML), where the authorization is setup in the web.config file such as
  1. <authorization>    
  2.     <deny users="?"/>    
  3. </authorization>   
But, for MVC app, most likely, people do the Authorization filter in the controller page by Attributes,
  1. using System.Web.Mvc;  
  2.   
  3. namespace SecurityDemoMVC.Controllers  
  4. {  
  5.     [Authorize]  
  6.     public class HomeController : Controller  
  7.     {  
  8.         [AllowAnonymous]  
  9.         public ActionResult Index()  
  10.         {  
  11.             return View();  
  12.         }  
  13.           
  14.         public ActionResult About()  
  15.         {  
  16.             ViewBag.Message = "Your application description page.";  
  17.   
  18.             return View();  
  19.         }  
  20.           
  21.         public ActionResult Contact()  
  22.         {  
  23.             ViewBag.Message = "Your contact page.";  
  24.   
  25.             return View();  
  26.         }  
  27.     }  
What we did,
  • Authentication
    • In the web.config file:
      • Configure Authentication Mode as Forms: <authentication mode="Forms">
    • In the AccountsController:
      • Database does the Authentication job
  • Authorization
    • In the AccountsController:
      • Add Authorization filters (Attributes) in either Controller level or Action level
      • FormsAuthentication Class does the Authorization job:
        • FormsAuthentication.SetAuthCookie(model.UserName, false);
        • FormsAuthentication.SignOut();
Run the App: the Home page will be viewable for anybody, but navigating to other pages will be redirected to the login page automatically because access is protected:
 
Home page
 
 
About page,
 
 

Summary

 
This article discusses the implementation of Forms Authentication by using its own database.
 
References