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:
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:
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:
Thanks in advance!
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); } } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With