Security in ASP.NET Web API and Entity Framework


Create a database with the name is LearnASPNETWebAPIWithRealApps. This database have 3 tables: Account, Role and Role_Account.

/* Table structure for table Account */

CREATE TABLE Account (
	Id int IDENTITY(1,1) NOT NULL PRIMARY KEY,
	Username varchar(250) NULL,
	Password varchar(250) NULL,
	FullName varchar(250) NULL,
	Enabled bit NOT NULL
)

/* Dumping data for table Account */

INSERT INTO Account(Username, Password, FullName, Enabled) VALUES
('acc1', '123', 'Account 1', 1)
INSERT INTO Account(Username, Password, FullName, Enabled) VALUES
('acc2', '123', 'Account 2', 0)
INSERT INTO Account(Username, Password, FullName, Enabled) VALUES
('acc3', '123', 'Account 3', 1)


/* Table structure for table Role */

CREATE TABLE Role (
  	Id int IDENTITY(1,1) NOT NULL PRIMARY KEY,
  	Name varchar(250) NOT NULL,
	Enabled bit NOT NULL
)


/* Dumping data for table Role */

INSERT INTO Role (Name, Enabled) VALUES('SuperAdmin', 1)
INSERT INTO Role (Name, Enabled) VALUES('Admin', 1)
INSERT INTO Role (Name, Enabled) VALUES('Employee', 1)


/* Table structure for table Role_Account */

CREATE TABLE Role_Account (
  	RoleId int NOT NULL,
  	AccountId int NOT NULL,
  	Enabled bit NOT NULL,
   	PRIMARY KEY (RoleId, AccountId),
   	FOREIGN KEY (AccountId) REFERENCES Account(Id),
   	FOREIGN KEY (RoleId) REFERENCES Role (Id)
)

--
-- Dumping data for table Role_Account
--

INSERT INTO Role_Account (RoleId, AccountId, Enabled) VALUES(1, 1, 1)
INSERT INTO Role_Account (RoleId, AccountId, Enabled) VALUES(2, 1, 1)
INSERT INTO Role_Account (RoleId, AccountId, Enabled) VALUES(2, 2, 1)
INSERT INTO Role_Account (RoleId, AccountId, Enabled) VALUES(3, 1, 1)
INSERT INTO Role_Account (RoleId, AccountId, Enabled) VALUES(3, 2, 1)
INSERT INTO Role_Account (RoleId, AccountId, Enabled) VALUES(3, 3, 1)

Structure of Account Table




Use the Entity Wizard to create an Entity Data Model From Database in Visual Studio.

Create a model class: AccountModel.cs in Models folder as below

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LearnASPNETWebAPIWithRealApps.Models
{
    public class AccountModel
    {
        private LearnASPNETWebAPIWithRealAppsEntities db = new LearnASPNETWebAPIWithRealAppsEntities();

        public Account find(string username)
        {
            return db.Accounts.SingleOrDefault(acc => acc.Username.Equals(username) && acc.Enabled == true);
        }

        public Account login(string username, string password)
        {
            return db.Accounts.SingleOrDefault(acc => acc.Username.Equals(username) && acc.Password.Equals(password) && acc.Enabled == true);
        }

    }
}

Create Security folder in server project. This folder contain classes need for security in ASP.NET Web API. Create classes: Credential.cs, MyAuthorize.cs and MyPrincipal.cs as below

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LearnASPNETWebAPIWithRealApps.Security
{
    public class Credential
    {
        public string Username
        {
            get;
            set;
        }

        public string Password
        {
            get;
            set;
        }

    }
}
using LearnASPNETWebAPIWithRealApps.Security;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;

namespace LearnASPNETWebAPIWithRealApps.Security
{
    public class MyAuthorize : AuthorizeAttribute
    {
        private const string BasicAuthResponseHeader = "WWW-Authenticate";
        private const string BasicAuthResponseHeaderValue = "Basic";

        public override void OnAuthorization(HttpActionContext actionContext)
        {
            try
            {
                AuthenticationHeaderValue authValue = actionContext.Request.Headers.Authorization;
                if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter) && authValue.Scheme == BasicAuthResponseHeaderValue)
                {
                    Credential parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
                    var myPrincipal = new MyPrincipal(parsedCredentials.Username);
                    if (!myPrincipal.IsInRole(Roles))
                    {
                        actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
                        actionContext.Response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
                        return;
                    }
                }
                else
                {
                    actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
                    actionContext.Response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
                    return;
                }
            }
            catch (Exception ex)
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
                actionContext.Response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
            }
        }

        private Credential ParseAuthorizationHeader(string authHeader)
        {
            string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(authHeader)).Split(new[] { ':' });
            if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1]))
                return null;
            return new Credential() { Username = credentials[0], Password = credentials[1], };
        }

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Web;
using LearnASPNETWebAPIWithRealApps.Models;

namespace LearnASPNETWebAPIWithRealApps.Security
{
    public class MyPrincipal : IPrincipal
    {
        private Account account;
        private AccountModel accountModel = new AccountModel();

        public MyPrincipal(string username)
        {
            this.Identity = new GenericIdentity(username);
            this.account = accountModel.find(username);
        }

        public IIdentity Identity
        {
            get;
            set;
        }

        public bool IsInRole(string role)
        {
            var roles = role.Split(new char[] { ',' });
            foreach (string r in roles)
            {
                var roleAccounts = this.account.Role_Account.ToList();
                foreach (var roleAccount in roleAccounts)
                {
                    if (roleAccount.Role.Name.Contains(r.Trim()))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
    }
}




Create Web API Controller provides text/plain data for the client

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using LearnASPNETWebAPIWithRealApps.Security;
using System.Net.Http.Headers;

namespace LearnASPNETWebAPIWithRealApps.Controllers
{
    [RoutePrefix("api/demo")]
    public class DemoRestController : ApiController
    {

        [HttpGet]
        [Route("work1")]
        [AllowAnonymous]
        public HttpResponseMessage Work1()
        {
            try
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK);
                response.Content = new StringContent("Work 1");
                response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
                return response;
            }
            catch
            {
                return new HttpResponseMessage(HttpStatusCode.BadRequest);
            }
        }

        [HttpGet]
        [Route("work2")]
        [MyAuthorize(Roles = "SuperAdmin")]
        public HttpResponseMessage Work2()
        {
            try
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK);
                response.Content = new StringContent("Work 2");
                response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
                return response;
            }
            catch
            {
                return new HttpResponseMessage(HttpStatusCode.BadRequest);
            }
        }

        [HttpGet]
        [Route("work3")]
        [MyAuthorize(Roles = "SuperAdmin,Admin")]
        public HttpResponseMessage Work3()
        {
            try
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK);
                response.Content = new StringContent("Work 3");
                response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
                return response;
            }
            catch
            {
                return new HttpResponseMessage(HttpStatusCode.BadRequest);
            }
        }

        [HttpGet]
        [Route("work4")]
        [MyAuthorize(Roles = "SuperAdmin,Admin,Employee")]
        public HttpResponseMessage Work4()
        {
            try
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK);
                response.Content = new StringContent("Work 4");
                response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
                return response;
            }
            catch
            {
                return new HttpResponseMessage(HttpStatusCode.BadRequest);
            }
        }

    }
}

Access Web API use the following url: http://localhost:64967/api/demo/work1

Output

Work 1

Access restful web services use the following url: http://localhost:64967/api/demo/work2

Output

Access restful web services use the following url: http://localhost:64967/api/demo/work3

Output

Access restful web services use the following url: http://localhost:64967/api/demo/work4

Output




Create Console Application Project in Visual Studio.

DemoRestClientModel class contain methods call Web API

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;

namespace LearnASPNETWebAPIWithRealApps_Client
{
    public class DemoRestClientModel
    {
        private string BASE_URL = "http://localhost:64967/api/demo/";
        private string username = "acc2";
        private string password = "123";

        public Task<HttpResponseMessage> work1()
        {
            try
            {
                HttpClient client = new HttpClient();
                client.BaseAddress = new Uri(BASE_URL);
                return client.GetAsync("work1");
            }
            catch
            {
                return null;
            }
        }

        public Task<HttpResponseMessage> work2_without_account()
        {
            try
            {
                HttpClient client = new HttpClient();
                client.BaseAddress = new Uri(BASE_URL);
                return client.GetAsync("work2");
            }
            catch
            {
                return null;
            }
        }

        public Task<HttpResponseMessage> work2()
        {
            try
            {
                HttpClient client = new HttpClient();
                var authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(this.username + ":" + this.password));
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authInfo);
                client.BaseAddress = new Uri(BASE_URL);
                return client.GetAsync("work2");
            }
            catch
            {
                return null;
            }
        }

        public Task<HttpResponseMessage> work3()
        {
            try
            {
                HttpClient client = new HttpClient();
                var authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(this.username + ":" + this.password));
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authInfo);
                client.BaseAddress = new Uri(BASE_URL);
                return client.GetAsync("work3");
            }
            catch
            {
                return null;
            }
        }

        public Task<HttpResponseMessage> work4()
        {
            try
            {
                HttpClient client = new HttpClient();
                var authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(this.username + ":" + this.password));
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authInfo);
                client.BaseAddress = new Uri(BASE_URL);
                return client.GetAsync("work4");
            }
            catch
            {
                return null;
            }
        }

    }
}

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace LearnASPNETWebAPIWithRealApps_Client
{
    class Program
    {
        static void Main(string[] args)
        {
            DemoRestClientModel demoRestClientModel = new DemoRestClientModel();

            Console.WriteLine("Test with work1 web method");
            HttpResponseMessage httpResponseMessage1 = demoRestClientModel.work1().Result;
            HttpStatusCode httpStatusCode1 = httpResponseMessage1.StatusCode;
            Console.WriteLine("\tStatus Code: " + httpStatusCode1);
            String result1 = httpResponseMessage1.Content.ReadAsStringAsync().Result;
            Console.WriteLine("\tResult: " + result1);

            Console.WriteLine("Test with work2 web method without account");
            HttpResponseMessage httpResponseMessage2 = demoRestClientModel.work2_without_account().Result;
            HttpStatusCode httpStatusCode2 = httpResponseMessage2.StatusCode;
            Console.WriteLine("\tStatus Code: " + httpStatusCode2);
            String result2 = httpResponseMessage2.Content.ReadAsStringAsync().Result;
            Console.WriteLine("\tResult: " + result2);

            Console.WriteLine("Test with acc2 have roles: admin and employee access work2 web method");
            HttpResponseMessage httpResponseMessage3 = demoRestClientModel.work2().Result;
            HttpStatusCode httpStatusCode3 = httpResponseMessage3.StatusCode;
            Console.WriteLine("\tStatus Code: " + httpStatusCode3);
            String result3 = httpResponseMessage3.Content.ReadAsStringAsync().Result;
            Console.WriteLine("\tResult: " + result3);

            Console.WriteLine("Test with acc2 have roles: admin and employee access work3 web method");
            HttpResponseMessage httpResponseMessage4 = demoRestClientModel.work3().Result;
            HttpStatusCode httpStatusCode4 = httpResponseMessage4.StatusCode;
            Console.WriteLine("\tStatus Code: " + httpStatusCode4);
            String result4 = httpResponseMessage4.Content.ReadAsStringAsync().Result;
            Console.WriteLine("\tResult: " + result4);

            Console.WriteLine("Test with acc2 have roles: admin and employee access work4 web method");
            HttpResponseMessage httpResponseMessage5 = demoRestClientModel.work4().Result;
            HttpStatusCode httpStatusCode5 = httpResponseMessage5.StatusCode;
            Console.WriteLine("\tStatus Code: " + httpStatusCode5);
            String result5 = httpResponseMessage5.Content.ReadAsStringAsync().Result;
            Console.WriteLine("\tResult: " + result5);

            Console.ReadLine();
        }
    }
}
Test with work1 web method
        Status Code: OK
        Result: Work 1
Test with work2 web method without account
        Status Code: Unauthorized
        Result:
Test with acc2 have roles: admin and employee access work2 web method
        Status Code: Unauthorized
        Result:
Test with acc2 have roles: admin and employee access work3 web method
        Status Code: OK
        Result: Work 3
Test with acc2 have roles: admin and employee access work4 web method
        Status Code: OK
        Result: Work 4