Authentication for Knockout/C# Web Application

We have an interesting situation: we’re developing a web application, using MVC4 that is heaving in Knockout. Mostly, I think, because the development thing thought it was cool, but also because the primary purpose of this application was to present data to the users from a database. That data needs to be presented in a number of different combinations. With KnockoutJS’s reputation for making tasks like that easier, it seemed to be a good fit.

However, not only will the data need to be presented on the web, it needs to expose web services so that users can use the data within their own applications.

And, on top of all of this, the user needs to be authenticated, and their level of permission known. This so we can determine which set of data to present (there will be multiple ones) and whether or not the user can do CRUD operations on the data.

So, I was presented the task of implementing user authentication. Since it took me a bit of time to figure it out, and I didn’t find alot of help on the web, I wanted to present what I did in order to help some other unlucky soul.

I should mention that this is code first programming. And that only the pertinent methods are shown in the Controllers; the CRUD you can figure out yourself.

Entities

    public class NameBaseEntity : IEntity
    {
        [Required]
        [DatabaseGeneratedAttribute(
              DatabaseGeneratedOption.Identity),Key()]
        public int Id { get; set; }

        [Required]
        [StringLength(256)]
        public string Name { get; set; }

        public DateTime Created_On { get; set; }
        public DateTime Modified_On { get; set; }
    }
    [Table("Group")]
    public class Group: NameBaseEntity
    {

    }
    [Table("User")]
    public class User: NameBaseEntity
    {
        [Required]
        public String Password { get; set; }
    }
   [Table("XRefUserGroup")]
    public class XRefUserGroup: IEntity
    {
        [Required]
        public int UserId { get; set; }

        [Required]
        public int GroupId { get; set; }

        [Key]
        public int Id
        {
            get;
            set; 
        }
        [Required]
        public System.DateTime Created_On
        {
            get;
            set;
        }
        [Required]
        public System.DateTime Modified_On
        {
            get;
            set;
        }
    }

Concrete Classes

    public class UserAuthentication: EntityOperations
    {

        public UserAuthentication(EFDbContext context, DbSet dataSet)
            : base(context, dataSet)
        {}

        /// Determine whether this user has provided the correct username and
        /// password to allow access to the system. If the user is valid,
        /// return the user id. If not, return 0.
        public int AuthenticateUser(String username, String password)
        {
            try
            {
                var userId = (from user in this._context.User
                              where user.Name == username && user.Password == password
                              select user.Id).Single();

                return userId;
            }
            catch (InvalidOperationException)
            {
                return 0;
            }

        }
    }
    public class UserAuthorization : EntityOperations
    {

        public UserAuthorization(EFDbContext context, DbSet dataSet)
            : base(context, dataSet)
        { }

        public bool IsUserMember(int UserId, int GroupId)
        {
            var isMember = from entry in this._context.XRefUserGroup
                           where entry.UserId == UserId && entry.GroupId == GroupId
                           select entry;
            return (isMember.Any());
        }

        public Dictionary<String, String> IsMemberOf(int UserId)
        {
            Dictionary<String, String> returnList = new Dictionary<String, String>();
            var memberGroups = from xref in this._context.XRefUserGroup
                               join groupName in this._context.Group on xref.GroupId equals groupName.Id
                               where xref.UserId == UserId
                               select new { GroupId = xref.GroupId, GroupName = groupName.Name };

            try
            {
                foreach (var dict in memberGroups)
                {
                    returnList.Add(dict.GroupId.ToString(), dict.GroupName);
                }
            }
            catch (InvalidOperationException) { }
            return returnList;                   
        }
    }

Controllers

   public class GroupController : Controller
    {
        private EFDbContext db = new EFDbContext();

       //
       // POST: /Group/PostGroupMembership/5
        public ActionResult PostGroupMembership(int memberId)
        {

            DbSet dbGroup = db.XRefUserGroup;

            HomeController controller = new HomeController();

            UserAuthorization auth = new UserAuthorization(db, dbGroup);
            UserAuthentication ua = new UserAuthentication(db, db.User);

            Dictionary<String, String> groupMembership = auth.IsMemberOf(memberId);

            User user = ua.GetItemById(memberId);

            Session.Add("Id", memberId);
            Session.Add("Uname", user.Name);
            Session.Add("Groups", String.Join(", ", groupMembership.Select(m => m.Key + ":" + m.Value).ToArray()));
            try
            {
                var tmp = groupMembership.Values.Where(p => p == "Administrator");
                Session.Add("IsAdmin",true);

            }
            catch (InvalidOperationException)
            {
                Session.Add("IsAdmin", false);
            }
            return RedirectToAction("Index", "Home");
        }
    public class UserController : ApiController
    {
        private DbContext db = new EFDbContext();

        /// Determine whether the user is authorized to use this system.

        public int GetAuthenticateUser(String user, String pass)
        {
            DbSet dbUser = db.User;

            UserAuthentication ua = new UserAuthentication(db, dbUser);

            return ua.AuthenticateUser(user, pass);
        }

        [HttpPost]
        public int PostAuthenticateUser(JObject identification)
        {
            DbSet dbUser = db.User;

            IList keys = identification.Properties().Select(p => p.Name).ToList();

            var obj = JObject.Parse(keys[0]);

            UserAuthentication ua = new UserAuthentication(db, dbUser);

            return ua.AuthenticateUser(obj["username"].Value(),obj["password"].Value());
        }
   }

KnockoutJS Code on the HTML Pages

var User = function (username, password) {
    var self = this;
    self.username = ko.observable();
    self.password = ko.observable();
    self.id = ko.observable(0);
    self.isAdmin = ko.observable(false);
    self.membership = ko.observable();
    self.hasError = ko.observable(false);

    self.id.subscribe(function (newValue) {
        this.hasError(newValue && newValue == 0);
    }, this);

    self.showName = function () {
        alert(self.username());
    };

    self.postLogin = function () {
        var jsonData = ko.toJSON(this);
        $.ajax({
            url: "/api/User/PostAuthenticateUser",
            type: "POST",
            data: jsonData,
            success: function (data) {
                self.id(data);
                if (data > 0) {
                    window.location.replace("/Group/PostGroupMembership?memberId=" + data);

                } else {

                    self.hasError(true);
                }
            },
            error: function () {
                self.id = 0;
            }
        });
    };

    return this;
};

On the Index page

    
   var Member = function () {
   var self = this;
   self.username = ko.observable('@Session["Uname"]');
   self.id = ko.observable('@Session["Id"]');
   self.isAdmin = ko.observable('@Session["IsAdmin"]' == 'True');
   self.membership = ko.observable('@Session["Groups"]');
};
$(document).ready(function () {
    var test = new Member();
    ko.applyBindings(test);
});