I’ve implemented Basic Authentication via 2 different methods (
- AuthorizeAttribute - http://www.dotnet-tricks.com/Tutorial/webapi/E3K8220314-Securing-ASP.NET-Web-API-using-basic-Authentication.html
- IHttpModule - http://www.asp.net/web-api/overview/security/basic-authentication
I have a fully operational solution on OneDrive (SkyDrive)
https://onedrive.live.com/embed?cid=E02420377ABA0395&resid=E02420377ABA0395%21437&authkey=AHneWrMqNmtML4c
but will include information for the first method as it requires the least amount of setup.
To quickly summarize the problem, both will send a 401 back to the client (Console Application) at which point the Authorization gets added to the header because of
client.Credentials = new NetworkCredential(userName, password);
and resent. I can see this in fiddler information but the authentication code(OnAuthorization) is never run again. It’s like something is intercepting it and automatically NAK’ing the message.
Fiddler picture link: https://onedrive.live.com/?cid=e02420377aba0395&id=E02420377ABA0395%21438&sff=1&authkey=%21AKuVE-ltUC0MKHw&v=3
As an additional test, I tried to add the U/P to the header directly using
Encoding encoding = Encoding.GetEncoding("iso-8859-1"); byte[] data = encoding.GetBytes(userName + ":" + password); //byte[] data = Encoding.ASCII.GetBytes(userName + ":" + password); string credentials = Convert.ToBase64String(data); client.Headers[HttpRequestHeader.Authorization] = string.Format("Basic {0}", credentials);
and this acted the same way in that it is being intercepted and NAK’ed.
Any thoughts on how to get it to authentication once the username/password (Authorization: Basic dXNlcjpwYXNzd29yZA==) is added to the header would be MOST appreciated.
Dave
Code and Project
You can create a new project using the following information and dropping in the code below.
- Visual Studio 2010
- ASP.NET MVC 4 Web Application (WebApi)
- Deleted extraneous stuff (scripts/content/etc)
- Added {action} to WebApiConfig.cs to look like routeTemplate: "api/{controller}/{action}/{id}",
- Web.Config - <authentication mode="None" />
- IIS
- Created Virtual Directory under Default Web Site using Visual Studio in Properties -> Web
- Changed Authentication for new Virtual Directory
- Disabled everything
- Enabled Anonymous Authentication
- Enabled Basic Authentication (HTTP 401 Challenge)
Client
using System; using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Net; using System.Runtime.Serialization.Json; using System.Text; namespace TestClient { class Program { private const string server = "localhost"; private const string url = "http://" + server + "/GenericWebService/api/"; //private const string url = "http://" + server + "/GenericWebServiceModule/api/"; const string userName = "user"; const string password = "password"; static void Main() { try { // // WebClient.UploadValues // PostMethod("values/Save_1"); //PostMethod("values/Save_2"); //PostMethod("values/Save_3"); //PostMethod("values/Save_4", true); //Trace.WriteLine(""); //PostMethod("values/Save_9"); // // WebRequest // PostMethod_2("values/Save_1"); } catch (Exception e) { Trace.WriteLine("\n" + e); } } static private WebClient GetClient() { var client = new WebClient { BaseAddress = url }; if (true) client.Credentials = new NetworkCredential(userName, password); else { Encoding encoding = Encoding.GetEncoding("iso-8859-1"); byte[] data = encoding.GetBytes(userName + ":" + password); //byte[] data = Encoding.ASCII.GetBytes(userName + ":" + password); string credentials = Convert.ToBase64String(data); client.Headers[HttpRequestHeader.Authorization] = string.Format("Basic {0}", credentials); } return client; } static private void PostMethod(string action, bool shouldDeserialize = false) { using (var client = GetClient()) { NameValueCollection values = new NameValueCollection(); values.Add("text", "Some Text"); values.Add("value", "500"); var result = client.UploadValues(action, "POST", values); string text = Encoding.ASCII.GetString(result); Trace.Write(action + " >> "); Trace.WriteLine(text); if (shouldDeserialize) { using (MemoryStream stream = new MemoryStream(result)) { DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof (Something)); Something x = ser.ReadObject(stream) as Something; Trace.WriteLine("\t" + x); } } } } static private void PostMethod_2(string action) { var request = (HttpWebRequest)HttpWebRequest.Create(url + "values/Save_1"); request.Credentials = new NetworkCredential(userName, password); request.PreAuthenticate = true; request.Method = "Post"; request.ContentType = "application/x-www-form-urlencoded"; using (StreamWriter sw = new StreamWriter(request.GetRequestStream())) { sw.Write("text=Some+Text&value=500"); } var response = (HttpWebResponse)request.GetResponse(); if (response.StatusCode == HttpStatusCode.OK) { using (StreamReader sr = new StreamReader(response.GetResponseStream())) { Trace.Write(action + " >> "); Trace.WriteLine(sr.ReadToEnd()); } } response.Close(); } } public class Something { public string text { get; set; } public string value { get; set; } public override string ToString() { return string.Format("Text: {0} Value: {1}", text, value); } } }
Custom Authorization
using System; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Principal; using System.Text; using System.Threading; using System.Web.Http; using System.Web.Http.Controllers; // http://www.dotnet-tricks.com/Tutorial/webapi/E3K8220314-Securing-ASP.NET-Web-API-using-basic-Authentication.html namespace GenericWebService.Security { public class CustomAuthorizeAttribute : AuthorizeAttribute { private const string BasicAuthResponseHeader = "WWW-Authenticate"; private const string BasicAuthResponseHeaderValue = "Basic"; public string UsersConfigKey { get; set; } public string RolesConfigKey { get; set; } protected CustomPrincipal CurrentUser { get { return Thread.CurrentPrincipal as CustomPrincipal; } set { Thread.CurrentPrincipal = value; } } public override void OnAuthorization(HttpActionContext actionContext) { try { AuthenticationHeaderValue authValue = actionContext.Request.Headers.Authorization; if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter) && authValue.Scheme == BasicAuthResponseHeaderValue) { Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter); if (parsedCredentials != null) { return; } } } catch { } actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized); actionContext.Response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue); } protected override bool IsAuthorized(HttpActionContext actionContext) { return base.IsAuthorized(actionContext); } private Credentials ParseAuthorizationHeader(string authHeader) { string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(authHeader)).Split(new[] { ':' }); if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1])) return null; return new Credentials { Username = credentials[0], Password = credentials[1] }; } } public class Credentials { public string Username { get; set; } public string Password { get; set; } } public class CustomPrincipal : IPrincipal { public IIdentity Identity { get; private set; } public bool IsInRole(string role) { return roles.Any(r => role.Contains(r)); } public CustomPrincipal(string Username) { Identity = new GenericIdentity(Username); } public int UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string[] roles { get; set; } } }
ValuesController
using System.Net.Http.Formatting; using System.Web.Http; using GenericWebService.Security; namespace GenericWebService.Controllers { [CustomAuthorize] public class ValuesController : ApiController { [HttpPost] public string Save_1(FormDataCollection data) { return string.Format("FormDataCollection - {0}: {1}", data.Get("text"), data.Get("value")); } [HttpPost] public string Save_2(Something data) { return string.Format("THIS IS A CLASS - {0}: {1}", data.text, data.value); } [HttpPost] public int Save_3(FormDataCollection data) { return int.Parse(data.Get("value")); } [HttpPost] public Something Save_4(FormDataCollection data) { return new Something { text = data.Get("text") + " x Junk", value = data.Get("value") + " x 123" }; } [HttpPost] public string Save_9([FromBody]string text, [FromBody]string value) { return string.Format("{0}: {1}", text, value); } } public class Something { public string text { get; set; } public string value { get; set; } } }