locked
WebApi Basic Authentication not authenticating second time after 401 Challenge RRS feed

  • Question

  • User639831915 posted

    I’ve implemented Basic Authentication via 2 different methods (

    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.

    <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 required

    Wednesday, 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