Asp.net-mvc – ASP.NET MVC 4 Web API Authentication with Membership Provider

asp.net-membershipasp.net-mvcasp.net-web-apifiddlerforms-authentication

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!

Best Answer

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);
            }
        }
    }
}