En este articulo veremos cómo hacer un inicio de sesión simple en un proyecto ASP.NET MVC

Utilizaremos Entity Framework para la conexión con la base de datos, por lo que recomiendo revisar el siguiente artículo para crear el modelo en Entity Framework: Abm simple en ASP.NET MVC y Entity Framework

Vamos a crear las tablas para los usuarios, tipo de usuarios y roles con el siguiente código SQL

CREATE TABLE TipoUsuario(
        TipoUsuarioId INT PRIMARY KEY IDENTITY (1, 1),
        Descripcion VARCHAR(50) NOT NULL
);

CREATE TABLE Rol(
	RolId INT PRIMARY KEY IDENTITY(1, 1),
	Descripcion VARCHAR(50) NOT NULL
);

CREATE TABLE Usuario(
	UsuarioId INT PRIMARY KEY IDENTITY(1, 1),
	NombreUsuario VARCHAR(50) NOT NULL,
	Clave VARCHAR(255) NOT NULL,
        TipoUsuarioId INT NOT NULL,
	RolId INT NOT NULL,
	Creado DATETIME NOT NULL,
	Modificado DATETIME NOT NULL,
	Activo BIT NOT NULL DEFAULT 1,
	CONSTRAINT UQ_NombreUsuario UNIQUE(NombreUsuario),
	CONSTRAINT FK_UsuarioTipo FOREIGN KEY (TipoUsuarioId) REFERENCES TipoUsuario(TipoUsuarioId),
	CONSTRAINT FK_UsuarioRol FOREIGN KEY (RolId) REFERENCES Rol(RolId)
);

INSERT INTO TipoUsuario (Descripcion) VALUES
('Administrador'),
('Usuario Normal');

INSERT INTO Rol (Descripcion) VALUES
('Administrador'),
('Vendedor');

Ahora debemos actualizar el modelo, para ello debemos abrir el modelo EDMX

Una vez que se abra el modelo hacemos click sobre cualquier espacio en blanco y seleccionamos la opción “Actualizar modelo desde base de datos…”

Una vez que abre el asistente seleccionamos dentro de tablas todas las tablas que fueron agregadas

Una vez que termine de generar el modelo debemos guardarlo

Podemos corroborar que generó correctamente el modelo abriendo el objeto EDMX en el explorador de soluciones, dentro del objeto ComercioModel.tt

Puede pasar que después de guardar no aparezcan las clases de las tablas dentro de ComercioModel.tt. Ante ese caso hacemos click sobre ComercioModel.tt y seleccionar la opción “Ejecutar herramienta personalizada”. Realizar lo mismo para el objeto ComercioModel.Context.tt

Una vez finalizado Compilar la solución para poder continuar codificando

Codificando Inicio de Sesión

Antes que nada debemos configurar el Web.config para que pida autenticación y se redirija a una página de inicio de sesión creada por nosotros, también se puede poner un timeout para que la sesión se venza cada determinado tiempo. dentro de Web.config busquen la etiqueta system.web y dentro de esta creen una nueva etiqueta llamada authentication como se muestra en el siguiente código

  <system.web>
    <authentication mode="Forms">
      <forms loginUrl="/Sesion/Iniciar" timeout="15"></forms>
    </authentication>
    <compilation debug="true" targetFramework="4.7.2" />
    <httpRuntime targetFramework="4.7.2" />
  </system.web>

Luego generamos una área llamada Admin, para saber cómo se genera área consulte este post: Areas en ASP MVC

Dentro de área Admin vamos a generar un controlador desde la entidad Usuario para que nos genera la funcionalidad completa. Dentro de la carpeta del área Admin hacemos click secundario sobre la carpeta Controllers y seleccionamos la opción “Agregar -> Controlador…”

En la ventana “Agregar scaffold” seleccionamos la opción “Controlador de MVC 5 con vistas que usa Entity Framework”

En la ventana “Agregar controlador” en el campo “Clase de modelo” seleccionamos la opción “Usuario”, en el campo “Clase de contexto de datos” seleccionamos “ComercioEntities” . Tildamos la opción “Generar vistas” y escribimos el nombre del controlador que por lo generar mantiene el nombre de la tabla en plural

Luego vamos al archivo de vista Areas/Admin/Views/Usuarios/Create.cshtml para quitar algunos campos que generamos automáticamente por código que son Activo, Creado y Modificado. También se cambia el tipo de Input Pasword al campo Clave para que el dato que se ingresa sea oculto. El código queda de la siguiente manera

@model Comercio.Usuario
@{
    ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    <div class="form-horizontal">
        <h4>Usuario</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.NombreUsuario, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.NombreUsuario, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.NombreUsuario, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Clave, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Clave, new { @class = "form-control", @type = "password" })
                @Html.ValidationMessageFor(model => model.Clave, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.TipoUsuarioId, "TipoUsuarioId", htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.DropDownList("TipoUsuarioId", null, htmlAttributes: new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.TipoUsuarioId, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.RolId, "RolId", htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.DropDownList("RolId", null, htmlAttributes: new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.RolId, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Antes de seguir con UsuariosController vamos a crear una clase para poder encriptar el campo Clave con SHA56 antes de guardar para ello agregaremos una carpeta llamada Global en el Proyecto

Hacemos click con el botón secundario sobre la nueva carpeta y seleccionamos la opción “Agregar -> Clase” a la que le daremos el nombre de Hasher.cs

Esta clase encriptará los valores que pasemos, como por ejemplo el campo Clave, luego lo combinará con una valor llamado salt que solo se debe cambiar de valor si se usa en otro proyecto. No se debe cambiar este valor si ya se guardaron usuarios con campos encriptados sino nunca volverá a coincidir cuando queramos iniciar sesión. El código de la clase es el siguiente:

using System;
using System.Security.Cryptography;
using System.Text;

namespace Comercio.Global
{
    public class Hasher
    {
        private static string salt = "7e33a455a9cfefa2671814a235cedeae44c1b12d5e1ca8b53448debedf46f929";

        public static string Hash(string pass)
        {
            string saltAndPwd = String.Concat(pass, salt);
            UTF8Encoding encoder = new UTF8Encoding();
            SHA256Managed sha256hasher = new SHA256Managed();
            byte[] hashedDataBytes = sha256hasher.ComputeHash(encoder.GetBytes(saltAndPwd));
            
            return byteArrayToString(hashedDataBytes);
        }

        private static string byteArrayToString(byte[] inputArray)
        {
            StringBuilder output = new StringBuilder("");
            for (int i = 0; i < inputArray.Length; i++)
            {
                output.Append(inputArray[i].ToString("X2"));
            }
            return output.ToString();
        }

        public static bool Compare(string hash, string pass)
        {
            return hash.Equals(Hash(pass));
        }
    }
}

Ahora que ya tenemos la clase tocamos la función [HttpPost] Create de UsuariosController

// POST: Admin/Usuarios/Create
// Para protegerse de ataques de publicación excesiva, habilite las propiedades específicas a las que desea enlazarse. Para obtener 
// más información vea https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Usuario usuario)
{
    if (ModelState.IsValid)
    {
        usuario.Activo = true;
        usuario.Creado = DateTime.Now;
        usuario.Modificado = DateTime.Now;
        usuario.Clave = Global.Hasher.Hash(usuario.Clave);
        db.Usuario.Add(usuario);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    ViewBag.RolId = new SelectList(db.Rol, "RolId", "Descripcion", usuario.RolId);
    ViewBag.TipoUsuarioId = new SelectList(db.TipoUsuario, "TipoUsuarioId", "Descripcion", usuario.TipoUsuarioId);
    return View(usuario);
}

Ejecutamos el proyecto e ingresamos a la pantalla Admin/Usuarios/Create, cargamos los datos para el usuario admin con una clave que recuerden, luego guardamos. Una vez guardado quedará como en la siguiente pantalla:

Antes que nada vamos a crear un modelo personalizado exclusivo para el inicio de sesión. Para ello agregaremos una clase sobre la carpeta Models que se encuentra al nivel del proyecto (fuera de la carpeta área) con el nombre SesionModel e ingresamos el siguiente código

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.ComponentModel.DataAnnotations;

namespace Comercio.Models
{
    public class SesionModel
    {
        [Display(Name = "Usuario")]
        [Required]
        public string NombreUsuario { get; set; }

        [Display(Name = "Contraseña")]
        [Required]
        [DataType(DataType.Password)]
        public string Clave { get; set; }
    }
}

Para la página de Inicio de sesión crearemos un controlador vació fuera del área llamada SesionController, para ello hacemos click con el botón secundario del mouse sobre la carpeta Controllers que se encuentra fuera del área.

En la pantalla siguiente seleccionamos la opción “Controlador de MVC 5: en blanco”. por ultimo ingresamos el nombre SesionController . Dentro del archivo Controllers/SesionController.cs ingresamos el siguiente código

using Comercio.Filters;
using Comercio.Global;
using Comercio.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

namespace Comercio.Controllers
{
    [Autenticacion(eTipoUsuario.Ninguno)]
    public class SesionController : Controller
    {
        ComercioEntities db = new ComercioEntities();

        // GET: Sesion
        public ActionResult Iniciar()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Iniciar(SesionModel model)
        {
            if (ModelState.IsValid)
            {
                var pass = Hasher.Hash(model.Clave);
                var user = db.Usuario
                    .Where(u => u.NombreUsuario == model.NombreUsuario && u.Clave == pass)
                    .FirstOrDefault();
                if(user != null)
                {
                    FormsAuthentication.SetAuthCookie(user.NombreUsuario, false);                    
                    return this.RedirectToAction("Index", "Default", new { Area = "Admin"});
                }
            }
            return View();
        }

        public ActionResult Salir()
        {
            FormsAuthentication.SignOut();
            return this.RedirectToAction("Iniciar");
        }
    }
}

Creamos una vista en Views/Sesion llamado Iniciar.cshtml que tendrá el siguiente código

@model Comercio.Models.SesionModel
@{
    ViewBag.Title = "Iniciar";
    Layout = "~/Views/Shared/_Sesion.cshtml";
}
<h2>Iniciar Sesión</h2>
@using (Html.BeginForm())
{
    <div class="row">
        <div class="col-6 form-group">
            @Html.LabelFor(m => m.NombreUsuario)
            @Html.TextBoxFor(m => m.NombreUsuario, new { @class = "form-control" })
        </div>
    </div>
    <div class="row">
        <div class="col-6 form-group">
            @Html.LabelFor(m => m.Clave)
            @Html.TextBoxFor(m => m.Clave, new { @type = "password", @class = "form-control" })
        </div>
    </div>
    <hr />
    <div class="row">
        <div class="col-12">
            <input type="submit" class="btn btn-success" value="Iniciar" />
        </div>
    </div>
}

Por último creamos un nuevo Layout que será exclusivo se para el inicio de sesión. Agregamos una vista en Views/Shared llamado _Sesion.cshtml el que tendrá el siguiente código

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
            <a class="navbar-brand" href="@Url.Action("Index", "Home", new { area = "" })">Comercio</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
        </nav>
    </header>
    <div class="container">
        @Html.Partial("~/Views/Shared/_Flash.cshtml")
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - Comercio</p>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")

    <script src="@Url.Content("~/Scripts/Global.js")"></script>

    @RenderSection("scripts", required: false)
</body>
</html>

Ahora en todos los controladores que se desee tener un inicio de sesión se agrega la etiqueta [Authorize] encima de la declaración de la clase del controlador, como por ejemplo

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using Comercio;

namespace Comercio.Areas.Admin.Controllers
{
    [Authorize]
    public class UsuariosController : Controller
    {
        private ComercioEntities db = new ComercioEntities();

        // GET: Admin/Usuarios
        public ActionResult Index()
        {
            var usuario = db.Usuario.Include(u => u.Rol).Include(u => u.TipoUsuario);
            return View(usuario.ToList());
        }

Si ejecutamos el proyecto e ingresamos a Admin/Usuarios nos rebota la petición y pide que ingresemos usuario y contraseña