Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do role based authorization for asp.net mvc 4 web api

I am trying to make a secure asp.net web api. For that I have followed the below link

MessageHandler for token

So now each and every api request needs a token which I am supplying in the request header as below for example

public class TestController : Controller
{

    public string GetProducts()
    {
        Uri myUri = new Uri("http://localhost:420420/api/products");
        WebRequest myWebRequest = WebRequest.Create(myUri);

        myWebRequest.Method = "GET";
        myWebRequest.ContentType = "application/json";
        myWebRequest.Headers.Add("Authorization-Token", RSAClass.accessToken);

        using (WebResponse response = myWebRequest.GetResponse())
        {
            using (var responseStream = response.GetResponseStream())
            {
                var reader = new StreamReader(responseStream);
                return reader.ReadToEnd();
            }
        }
    }    
  }

So I am now able to make each and every api request, check for a token in the header. But how do I accomplish authorization, I mean how can I not allow this token not access some actions in the same controller.I just need an idea.Hope I explained well enough.

Edit:

public class TestController : Controller
{
    public string GetProducts()
    {
        Uri myUri = new Uri("http://localhost:420420/api/products");         

        WebRequest myWebRequest = WebRequest.Create(myUri);

        myWebRequest.Method = "GET";
        myWebRequest.ContentType = "application/json";
        myWebRequest.Headers.Add("Authorization-Token", RSAClass.accessToken);

        **using (WebResponse response = myWebRequest.GetResponse())
        {
            using (var responseStream = response.GetResponseStream())
            {
                var reader = new StreamReader(responseStream);
                return reader.ReadToEnd();
            }
        }**
 }

I am making a request to the "api" controller, inside above controller, using webrequest(I will change it later to HttpClient). In the code between ** ** above I am getting 404 page not found for myWebRequest.GetResponse()

Below is my api controller

public class ProductsController : ApiController
{

    TestModelContainer testModel = new TestModelContainer();

    [Authorize(Roles="Users")]
    public IEnumerable<Products> GetProducts()
    {
        IEnumerable<Products> products = (from prods in testModel.Products
                        select prods);
        return products;        
    }        
 }
}

Now in the delegating handler I have the following code

public class TokenValidationHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
     CancellationToken cancellationToken)
    {
        TestModelContainer testModel = new TestModelContainer();

        var token = "";
        try
        {

            if (request.Headers.Contains("Authorization-Token"))
            {

                token = request.Headers.GetValues("Authorization-Token").FirstOrDefault();

                if (String.IsNullOrEmpty(token))
                {
                    return Task<HttpResponseMessage>.Factory.StartNew(() =>
                    {
                        return new HttpResponseMessage(HttpStatusCode.BadRequest)
                        {
                            Content = new StringContent("Missing Authorization-Token")
                        };
                    });
                }
            }
            else
            {
                return Task<HttpResponseMessage>.Factory.StartNew(() =>
                {
                    return new HttpResponseMessage(HttpStatusCode.BadRequest)
                    {
                        Content = new StringContent("You need to include Authorization-Token " +
                        "header in your request")
                    };
                });
            }


            var decryptedToken = RSAClass.Decrypt(token);
            var foundUser =  (from user in testModel.Users
                                where user.Name == decryptedToken
                                select user).Any();              

            if (!foundUser)
                return Task<HttpResponseMessage>.Factory.StartNew(() =>
                {
                    return new HttpResponseMessage(HttpStatusCode.Forbidden)
                    {
                        Content = new StringContent("Unauthorized User")
                    };
                });

      var identity = new GenericIdentity(decryptedToken);
              string[] roles = new string[] { "Users", "Testers" };

              var principal = new GenericPrincipal(identity, roles);
              Thread.CurrentPrincipal = principal;
        }
        catch (Exception ex)
        {
            return Task<HttpResponseMessage>.Factory.StartNew(() =>
            {
                return new HttpResponseMessage(HttpStatusCode.InternalServerError)
                {
                    Content = new StringContent("Error encountered while attempting to process authorization token")
                };
            });
        }
        return base.SendAsync(request, cancellationToken);
    }

The 404 error doesnt rise if i remove the Authorize attribute from the api controller, and then I am able to access it.

Update(I believe solution too):

this is how the issue got solved

I have changed the TestController method as below suggested by Darin Dimitrov

public class TestsController : Controller
{
    public ActionResult GetProducts()
    {
        var productsUrl = Url.RouteUrl("DefaultApi", new { httproute = "", controller = "products" }, "http");
        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Add("Authorization-Token", RSAClass.accessToken);

         var products = client
               .GetAsync(productsUrl)
                 .Result;

            if (products.StatusCode == HttpStatusCode.Unauthorized)
            {
                return Content("Sorry you are not authorized to perform this operation");
            }

            var prods = products.Content
                .ReadAsAsync<IEnumerable<Products>>()
                .Result;

            return Json(prods, JsonRequestBehavior.AllowGet);
        }
    }

The issue was I didnt know how to make a call to the api, thanks to Darin for his great support(he was very quick too).

Thanks

like image 943
CrazyNooB Avatar asked Aug 16 '12 12:08

CrazyNooB


1 Answers

You register the handler in Global.asax:

GlobalConfiguration
    .Configuration
    .MessageHandlers
    .Add(new TokenValidationHandler());

and then decorate controllers/actions that require authorization with the [Authorize] attribute:

public class MyController : ApiController
{
    [Authorize]
    public string Get(string id)
    {
        ...          
    }
}

For role based authorization you may could take a look at the following example: https://stackoverflow.com/a/11536349/29407

It uses basic authentication over SSL and relies on the built-in membership and role providers.


UPDATE:

According to the numerous comments left I get the impression that my answer was not clear enough. Let me elaborate.

  1. Create a new ASP.NET MVC 4 project using the Empty Template
  2. Define a model:

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    
  3. Define an ApiController:

    public class ProductsController : ApiController
    {
        // GET /api/products => only users having the Users role can call this
        [Authorize(Roles = "Users")]
        public HttpResponseMessage Get()
        {
            var products = Enumerable.Range(1, 5).Select(x => new Product
            {
                Id = x,
                Name = "product " + x
            });
            return Request.CreateResponse(HttpStatusCode.OK, products);
        }
    
        // GET /api/products => only users having the Admin role can call this
        [Authorize(Roles = "Admin")]
        public void Post(Product product)
        {
        }
    }
    
  4. Define a RSAHelper:

    public class RSAClass
    {
        private static string _privateKey = "<RSAKeyValue><Modulus>poQS/c9tLkgg84xYZpnUBHP6fy24D6XmzhQ8yCOG317hfUNhRt6Z9N4oTn+QcOTh/DAnul4Q901GrHbPrMB8tl1LtbpKbvGftPhyR7OLQVnWC1Oz10t2tHEo7mqyPyAVuYsq8Q1E3YNTh2V6+PRvMiAWGUHGyyG7fKjt/R9W+RE=</Modulus><Exponent>AQAB</Exponent><P>4G09wYejA4iLakpAcjXbE/zV9tXTNsYqVIWeXF4hzwMmwmin7ru/WQzXu2DdapXXOJIKqrkfzXlcPwCsW5b9rQ==</P><Q>vfEq13Et+cP4eGgsR+crDQH0Mi+G6UW5ACfuDs/zam1o+CE70pLgeWawfqW4jRN30/VHDnTF9DZuotH6zihNdQ==</Q><DP>JoZaHYidERQ1am+IlJJuIwY57H9UHIjz50JwpsZ540FVO/YfLboI5M5xkfbUy2EhatKXBit1LB5zGVWSQL6wmQ==</DP><DQ>Gxk7KX2GN6oT2unR13hNlg9/TWGmd8VwvWr09bwJWFe/sBbduA8oY2mZKJhwGgB7CgxmVNOoIk1Zv3UBuUPauQ==</DQ><InverseQ>ZwJpSUZ09lCfiCF3ILB6F1q+6NC5hFH0O4924X9B4LZ8G4PRuudBMu1Yg0WNROUqVi3zfihKvzHnquHshSL56A==</InverseQ><D>pPQNRDVpeQGm8t1C7VDRwR+LNNV7krTMMbXGiJT5FOoPAmHvSZ9WcEZrM2gXFF8IpySlFm/86p84tbx0+jMs1niU52VsTscsamGbTzbsxeoHAt1fQUvzYveOGoRezotXblboVB2971r6avMHNtAk0FAdjvh4TjGZJCGTqNHD0mE=</D></RSAKeyValue>";
        private static string _publicKey = "<RSAKeyValue><Modulus>poQS/c9tLkgg84xYZpnUBHP6fy24D6XmzhQ8yCOG317hfUNhRt6Z9N4oTn+QcOTh/DAnul4Q901GrHbPrMB8tl1LtbpKbvGftPhyR7OLQVnWC1Oz10t2tHEo7mqyPyAVuYsq8Q1E3YNTh2V6+PRvMiAWGUHGyyG7fKjt/R9W+RE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
        private static UnicodeEncoding _encoder = new UnicodeEncoding();
    
        public static string Decrypt(string data)
        {
            try
            {
                var rsa = new RSACryptoServiceProvider();
                var dataArray = data.Split(new char[] { ',' });
    
                byte[] dataByte = new byte[dataArray.Length];
                for (int i = 0; i < dataArray.Length; i++)
                {
                    dataByte[i] = Convert.ToByte(dataArray[i]);
                }
    
                rsa.FromXmlString(_privateKey);
                var decryptedByte = rsa.Decrypt(dataByte, false);
                return _encoder.GetString(decryptedByte);
            }
            catch (Exception)
            {
                throw new RSAException();
            }
        }
    
        public static string Encrypt(string data)
        {
            try
            {
                var rsa = new RSACryptoServiceProvider();
                rsa.FromXmlString(_publicKey);
                var dataToEncrypt = _encoder.GetBytes(data);
                var encryptedByteArray = rsa.Encrypt(dataToEncrypt, false).ToArray();
                var length = encryptedByteArray.Count();
                var item = 0;
                var sb = new StringBuilder();
                foreach (var x in encryptedByteArray)
                {
                    item++;
                    sb.Append(x);
    
                    if (item < length)
                        sb.Append(",");
                }
    
                return sb.ToString();
    
            }
            catch (Exception ex)
            {
                throw new RSAException();
            }
        }
    
        public class RSAException : Exception
        {
            public RSAException() : base("RSA Encryption Error") { }
        }
    }
    
  5. Define a TokenValidationHandler:

    public class TokenValidationHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            try
            {
                if (!request.Headers.Contains("Authorization-Token"))
                {
                    return Task<HttpResponseMessage>.Factory.StartNew(() =>
                    {
                        return new HttpResponseMessage(HttpStatusCode.BadRequest)
                        {
                            Content = new StringContent("You need to include Authorization-Token header in your request")
                        };
                    });
                }
    
                var token = request.Headers.GetValues("Authorization-Token").FirstOrDefault();
                if (string.IsNullOrEmpty(token))
                {
                    return Task<HttpResponseMessage>.Factory.StartNew(() =>
                    {
                        return new HttpResponseMessage(HttpStatusCode.BadRequest)
                        {
                            Content = new StringContent("Missing Authorization-Token")
                        };
                    });
                }
    
                var decryptedToken = RSAClass.Decrypt(token);
    
                // TODO: do your query to find the user
                var user = decryptedToken;
    
                var identity = new GenericIdentity(decryptedToken);
                string[] roles = new[] { "Users", "Testers" };
    
                var principal = new GenericPrincipal(identity, roles);
                Thread.CurrentPrincipal = principal;
            }
            catch
            {
                return Task<HttpResponseMessage>.Factory.StartNew(() =>
                {
                    return new HttpResponseMessage(HttpStatusCode.InternalServerError)
                    {
                        Content = new StringContent("Error encountered while attempting to process authorization token")
                    };
                });
            }
    
            return base.SendAsync(request, cancellationToken);
        }
    }
    
  6. Define a test controller:

    public class TestsController : Controller
    {
        public ActionResult GetProducts()
        {
            var productsUrl = Url.RouteUrl("DefaultApi", new { httproute = "", controller = "products" }, "http");
            using (var client = new HttpClient())
            {
                var token = RSAClass.Encrypt("john");
                client.DefaultRequestHeaders.Add("Authorization-Token", token);
    
                var products = client
                    .GetAsync(productsUrl)
                    .Result
                    .Content
                    .ReadAsAsync<IEnumerable<Product>>()
                    .Result;
    
                return Json(products, JsonRequestBehavior.AllowGet);
            }
        }
    
        public ActionResult PostProduct()
        {
            var productsUrl = Url.RouteUrl("DefaultApi", new { httproute = "", controller = "products" }, "http");
            using (var client = new HttpClient())
            {
                var token = RSAClass.Encrypt("john");
                client.DefaultRequestHeaders.Add("Authorization-Token", token);
    
                var product = new Product 
                {
                    Id = 1,
                    Name = "test product"
                };
    
                var result = client
                    .PostAsync<Product>(productsUrl, product, new JsonMediaTypeFormatter())
                    .Result;
                if (result.StatusCode == HttpStatusCode.Unauthorized)
                {
                    return Content("Sorry you are not authorized to perform this operation");
                }
    
                return Json(true, JsonRequestBehavior.AllowGet);
            }
        }
    }
    
  7. Test:

    * /tests/getproducts => success
    * /tests/postproduct => 401
    
like image 64
Darin Dimitrov Avatar answered Sep 20 '22 17:09

Darin Dimitrov