Answered by:
WebApi Basic Authentication not authenticating second time after 401 Challenge

Question
-
User639831915 posted
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=AHneWrMqNmtML4cbut 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.
<iframe width="302" height="320" src="https://onedrive.live.com/embed?cid=E02420377ABA0395&resid=E02420377ABA0395%21438&authkey=AKuVE-ltUC0MKHw" frameborder="0" scrolling="no"></iframe>
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; } } }
Monday, December 8, 2014 1:50 PM
Answers
-
User639831915 posted
I hacked this out of my code and cleaned it up so that it was more concise and could post it here.
This is essentially what I did which is loosely based on some obscure post I found somewhere. Don't remember where anymore.
Hope it helps.
var credential = new NetworkCredential(username, password, domain); var basicAuthentication = Base64Encode(string.Format("{0}:{1}", credential.UserName, credential.Password), Encoding.ASCII); var request = (HttpWebRequest)WebRequest.Create(url); request.Headers.Add(HttpRequestHeader.Authorization, new AuthenticationHeaderValue("Basic", basicAuthentication).ToString()); public string Base64Encode(string text, Encoding encoding) { byte[] bytes = encoding.GetBytes(text); return Convert.ToBase64String(bytes); }
- Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
Wednesday, June 3, 2015 9:22 AM
All replies
-
User-782957977 posted
Did you register Http module in Web.Config as given in http://www.asp.net/web-api/overview/security/basic-authentication?
Tuesday, December 9, 2014 10:50 PM -
User639831915 posted
IHttpModule implementation Yes - otherwise I wouldn't have gotten into the Authorization the first time
AuthorizeAtribute implementation No - not requiredWednesday, December 10, 2014 12:09 PM -
User1045201862 posted
Hi I've also the same problem. First time it authenticates okay, the second time.. it fails with a 401. Any clue?
Wednesday, May 27, 2015 5:32 AM -
User639831915 posted
I hacked this out of my code and cleaned it up so that it was more concise and could post it here.
This is essentially what I did which is loosely based on some obscure post I found somewhere. Don't remember where anymore.
Hope it helps.
var credential = new NetworkCredential(username, password, domain); var basicAuthentication = Base64Encode(string.Format("{0}:{1}", credential.UserName, credential.Password), Encoding.ASCII); var request = (HttpWebRequest)WebRequest.Create(url); request.Headers.Add(HttpRequestHeader.Authorization, new AuthenticationHeaderValue("Basic", basicAuthentication).ToString()); public string Base64Encode(string text, Encoding encoding) { byte[] bytes = encoding.GetBytes(text); return Convert.ToBase64String(bytes); }
- Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
Wednesday, June 3, 2015 9:22 AM