Secure Web API with Form Authentication (Authorize) and Basic Authentication (Message Handler) together

13,180

Solution 1

There are many examples online as to how best to wrap Web API using Authorization (see Toan's answer). I do know it's possible to secure web api with Forms Authentication tokens and use the attributes in your example. I don't believe you should have to write your own Message Handler to do it either.

http://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api

Start by looking at this web page. Also, Toan's answer and link are also great suggestions. You are certainly on the right track. You can also get a good feel for how security works in ASP.NET Web API by building MVC Templates with Authentication as those samples include an Account Management Controller and all the code to do authentication and authorization.

Assuming you have properly setup Forms Authentication within your website. When the Web API Method is called (Get, Post, Put, Delete, Options) the Controller.User will be a populated IPrincipal object containing a Name, IsAuthenticated bool and a List of Roles. These values are controlled by your Forms Authentication piece AND are what are queried by the framework when you use the [AllowAnonymous] or [Authorize] attributes.

Please Note: Using Forms Authentication without SSL is a really, really bad thing as credentials are shared as clear text. Forms Auth is also susceptible to Cross Site Request Forgery

Here is an example I use in MVC4 to perform Forms Authentication on Web API using a Super Class called BaseApiController

    public BaseApiController()
    {
        CurrentUser = new ScrubbedUser(User);
    }
    protected ScrubbedUser CurrentUser { get; set; }

Then in my ScrubbedUser Class I retrieve the User's information from a Database (or Cache/Session) remembering that the user could be Anonymous

public class ScrubbedUser
    {
        private IPrincipal Principal { get; set; }
        public ScrubbedUser(string principal)
        {
            Principal = null;
            if (string.IsNullOrEmpty(principal))
            {
                Profile = GetDefaultProfile();
            }
            else
            {
                Profile = GetUserProfile(principal);
            }
            //Get User Memberships
            Memberships = GetUserMemberships();
            Settings = GetUserSettings();
        }
        public SurgeStreetUser(IPrincipal principal) 
        {
            Principal = principal;
            if (Principal == null
                || Principal.Identity == null
                || Principal.Identity.IsAuthenticated == false
                || string.IsNullOrEmpty(Principal.Identity.Name))
            {
                Profile = GetDefaultProfile();
            }
            else
            {
                Profile = GetUserProfile(Principal.Identity.Name);
            }
            //Get User Memberships
            Memberships = GetUserMemberships();
            Settings = GetUserSettings();
        }
        public UserProfile Profile { get; private set; }
        public List<V_UserMembership> Memberships { get; private set; }
        public List<Setting> Settings { get; private set; }

        private UserProfile GetDefaultProfile()
        {
            //Load an Anonymous Profile into the ScrubbedUser instance any way you like
        }
        private UserProfile GetUserProfile(string userName)
        {
            //Load the UserProfile based on userName variable (origin is Principle Identity Name
        }
        private List<V_UserMembership> GetUserMemberships()
        {
            //Load User's Memberships or Roles from Database, Cache or Session
        }
        private UserProfile PopulateCurrentUser(UserProfile userProfile)
        {
            var user = new UserProfile
            {
                //Convenience Method to clone and set a Profile Property
            };
            return user;
        }
        private List<Setting> GetUserSettings()
        {
            //Get the User's Settings or whatever
        }
        //Convenience to return a JSON string of the User (great to use with something like Backbone.js)
        private dynamic JSONRecord
        {
            get
            {
                return new
                {
                    CustId = Profile.CustId,
                    UserName = Profile.UserName,
                    UserId = Profile.UserId,
                    Email = Profile.Email,
                    FirstName = Profile.FirstName,
                    Language = Profile.Language,
                    LastActivityDate = Profile.LastActivityDate,
                    LastName = Profile.LastName,
                    DebugOption = Profile.DebugOption,
                    Device = Profile.Device,
                    Memberships = Memberships,
                    Settings = Settings
                };
            }
        }
    }

I use Memberships instead of Roles and can use the CurrentUser property of the Super Class to test to see if the user is a member of something. I can also use the [Authorize] attribute on the Web API Controller at the class level or at the method level

public class ListController : BaseApiController
{
    //main is "global"
    public dynamic Get(string id)//JObject values)
    {
        //I can test here for anonymous as well, even if I allow anonymous

        //Example using my own convention on User Profile Class populated by ScrubbedUser constructor
        if (CurrentUser.Profile.CustId == "public")
        {
            return HttpStatusCode.Forbidden;
        }
        //Redundant Code
        if (!User.Identity.IsAuthenticated)
        {
            return HttpStatusCode.Forbidden;
        }
        string filterExt = string.IsNullOrEmpty(id) || id=="global"
            ? "*" : id;
        return ListRepository.GetList(filterExt, SSUser);
    }
    [Authorize]
    public dynamic Post(JObject values)
    {
        //Just a sample, this will not fire unless the user is authenticated
        return CurrentUser.JSONRecord;
    }
}

Solution 2

I think there are some misunderstanding in terms of how to secure Web API. What is the purpose of using Forms-based authentication here?

You have the basic authentication which authenticates a user. Why do you need to use Forms-based authentication to authenticate the user against?

If you want to verify user's permissions, then map the user credential to a set of claims and check it on your API controller.

You can have a look at the following link http://channel9.msdn.com/Shows/Web+Camps+TV/Securing-ASPNET-Web-APIs

Share:
13,180
chemitaxis
Author by

chemitaxis

Updated on June 07, 2022

Comments

  • chemitaxis
    chemitaxis almost 2 years

    I am trying to use Form Authentication (Filter Attribute) and Basic Authentication (Message Handler) together. I know that the pipeline of the Web API executes before the Message Handler than Filters.

    I have tried everything and I have tried to search in google a lot but I can't find a solution, I only can do work the authentication and authorice separated but not together.

    Code:

    TestController.cs

    public class TestController : ApiController
    {
        private readonly worldEntities  _db = new worldEntities();
    
        // GET api/Country
        [Authorize]
        public Capital Get()
        {
    
            var capital = new Capital
            {
                CapitalCountry = _db.cities.FirstOrDefault(),
                Country = _db.countries.FirstOrDefault()
            };
            capital.Cities = _db.cities.Where(s => s.CountryCode == capital.Country.Code);
            _db.SaveChanges();
            return capital;
    
        }
    
    
        // Post api/Country
        public Capital Post()
        {
    
            var capital = new Capital
            {
                CapitalCountry = _db.cities.FirstOrDefault(),
                Country = _db.countries.FirstOrDefault()
            };
            capital.Cities = _db.cities.Where(s => s.CountryCode == capital.Country.Code);
            _db.SaveChanges();
            return capital;
    
        }
    
    }
    

    WebApiConfig.cs

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            config.MessageHandlers.Add(new BasicAuthMessageHandler());
    
            config.Filters.Add(new AuthorizeAttribute());
    
            // Web API routes
            config.MapHttpAttributeRoutes();
    
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
    

    BasiAuthMessagHandle.cs

    public class BasicAuthMessageHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            var headers = request.Headers;
            if (headers.Authorization != null && headers.Authorization.Scheme == "Basic")
            {
                var userPwd = Encoding.UTF8.GetString(Convert.FromBase64String(headers.Authorization.Parameter));
                var user = userPwd.Substring(0, userPwd.IndexOf(':'));
                var password = userPwd.Substring(userPwd.IndexOf(':') + 1);
                // we suppose that it's ok
                var principal = new GenericPrincipal(new GenericIdentity(user), null);
                PutPrincipal(principal);
    
            }
    
            return base.SendAsync(request, cancellationToken);
        }
    
        private void PutPrincipal(IPrincipal principal)
        {
            Thread.CurrentPrincipal = principal;
            if (HttpContext.Current != null)
            {
                HttpContext.Current.User = principal;
            }
        }
    
    
    }
    

    AuthController.cs

    public class AuthController : ApiController
    {
    
            public string Get(string id)
            {
                FormsAuthentication.SetAuthCookie(id ?? "FooUser", false);
                return "You are autenthicated now";
            }
    
    }
    

    Web.Config

    <authentication mode="Forms" />
    

    Thank you so much!!