Search code examples
asp.net-mvcasp.net-membershipforms-authenticationasp.net-web-apifiddler

ASP.NET MVC 4 Web API Authentication with Membership Provider


I have an ASP.NET MVC 4 Project using the Web API. On the controller I have set the class to require authorization using the [Authorize] attribute. For Authentication I am using the ASP.NET Membership Provider and have my Web.Config set to use "Forms" Authentication. Here is where I am stuck:

Everything is working great up until the point that I am done with testing the API and I want to secure the controller with the [Authorize] attribute so I can start testing authentication against users in my Membership Provider. So I fire up Fiddler and make the same call adding the Authorization:Basic attribute along with a username:password from my membership provider like so:

enter image description here

The response I get is 401 unauthorized and under "Auth" I get "No WWW-Authenticate Header is present." Then I realize that the API is looking for an SHA1 encoded key. So I fire up an SHA1 generator from a search and get a hash for my username:password and update my Request Header like so:

enter image description here

This does not work either and I get the same results. Also I obviously need some sort of "shared secret key" to use with the server to decode my username/password.

So my questions:

  1. How do I get this key from the server (or in this case Virtual IIS running off VS 2012).
  2. How do I use this to make Authenticated calls in Fiddler using usernames/passwords from an ASP.NET Membership Provider.
  3. How will I use this in my client application to make the same calls (C# WPF App).
  4. Is this best practive when combined with SSL on my HTTP calls? If not what is?

Thanks in advance!


Solution

  • You could use basic authentication with SSL. On the server side we could write a custom delegating handler which will verify the credentials by querying the memebership provider that we registered, and if valid, retrieve the roles and set the current principal:

    public class BasicAuthenticationMessageHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var authHeader = request.Headers.Authorization;
    
            if (authHeader == null)
            {
                return base.SendAsync(request, cancellationToken);
            }
    
            if (authHeader.Scheme != "Basic")
            {
                return base.SendAsync(request, cancellationToken);
            }
    
            var encodedUserPass = authHeader.Parameter.Trim();
            var userPass = Encoding.ASCII.GetString(Convert.FromBase64String(encodedUserPass));
            var parts = userPass.Split(":".ToCharArray());
            var username = parts[0];
            var password = parts[1];
    
            if (!Membership.ValidateUser(username, password))
            {
                return base.SendAsync(request, cancellationToken);
            }
    
            var identity = new GenericIdentity(username, "Basic");
            string[] roles = Roles.Provider.GetRolesForUser(username);
            var principal = new GenericPrincipal(identity, roles);
            Thread.CurrentPrincipal = principal;
            if (HttpContext.Current != null)
            {
                HttpContext.Current.User = principal;
            }
    
            return base.SendAsync(request, cancellationToken);
        }
    }
    

    We then register this handler in Application_Start:

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

    Now we could have an Api controller that will be decorated with the [Authorize] attribute to ensure that only authenticated users can access its actions:

    [Authorize]
    public class ValuesController : ApiController
    {
        public string Get()
        {
            return string.Format("Hello {0}", User.Identity.Name);
        }
    }
    

    Alright, now let's look at a sample client:

    using System;
    using System.Net;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    
    class Program
    {
        static void Main()
        {
            // since for testing purposes I am using IIS Express
            // with an invalid SSL certificate I need to desactivate
            // the check for this certificate.
            ServicePointManager.ServerCertificateValidationCallback += 
                (sender, certificate, chain, sslPolicyErrors) => true;
    
            using (var client = new HttpClient())
            {
                var buffer = Encoding.ASCII.GetBytes("john:secret");
                var authHeader = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(buffer));
                client.DefaultRequestHeaders.Authorization = authHeader;
                var task = client.GetAsync("https://localhost:44300/api/values");
                if (task.Result.StatusCode == HttpStatusCode.Unauthorized)
                {
                    Console.WriteLine("wrong credentials");
                }
                else
                {
                    task.Result.EnsureSuccessStatusCode();
                    Console.WriteLine(task.Result.Content.ReadAsAsync<string>().Result);
                }
            }
        }
    }