Take advantage of HTTP authentication to secure your Web API In this article I would present a discussion on implementing HTTP authentication in Web API. There are two ways in which you can implement HTTP authentication in your Web Api. These include: Forms authentication Basic authentication We wouldn’t consider Windows authentication as a feasible strategy as you cannot expose your service over the Internet if you leverage Windows authentication. Securing Web Api using Forms Authentication Forms authentication uses the ASP.Net membership provider and uses standard HTTP cookies instead of the Authorization header. Forms authentication is not that REST-friendly as it uses cookies, and the clients would need to manage cookies to consume services that take advantage of forms authentication, which is vulnerable to cross-site forgery attacks. This is why you would need to implement CSRF measures if you use forms authentication. Forms authentication doesn’t use encryption to secure the user’s credentials. Hence, this is not a secure strategy unless you run your Web API over SSL. Secure Web API using basic authentication Basic authentication sends the user’s credentials in plaint text over the wire. If you were to use basic authentication, you should use your Web API over a Secure Socket Layer (SSL). When using basic authentication, we would pass the user’s credentials or the authentication token in the header of the HTTP request. The service at the server side would need to parse the header to retrieve the authentication token. If the request is not a valid request, the server returns HTTP 401, meaning an unauthorized response. Let’s explore how we can perform basic authentication using an action filter. To do this, you should create a class that derives the System.Web.Http.Filters.ActionFilterAttribute class as shown below: public class BasicAuthenticationAttribute : System.Web.Http.Filters.ActionFilterAttribute { private Boolean IsUserValid(Dictionary<string, string> credentials) { if (credentials["UserName"].Equals("joydip") && credentials["Password"].Equals("joydip123")) return true; return false; } private Dictionary<string, string> ParseRequestHeaders(System.Web.Http.Controllers.HttpActionContext actionContext) { Dictionary<string, string> credentials = new Dictionary<string, string>(); var httpRequestHeader = actionContext.Request.Headers.GetValues("Authorization").FirstOrDefault(); httpRequestHeader = httpRequestHeader.Substring("Authorization".Length); string[] httpRequestHeaderValues = httpRequestHeader.Split(':'); string username = Encoding.UTF8.GetString(Convert.FromBase64String(httpRequestHeaderValues[0])); string password = Encoding.UTF8.GetString(Convert.FromBase64String(httpRequestHeaderValues[1])); credentials.Add("UserName", username); credentials.Add("Password", password); return credentials; } public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) { try { if (actionContext.Request.Headers.Authorization == null) { actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); } else { Dictionary<string, string> credentials = ParseRequestHeaders(actionContext); if (IsUserValid(credentials)) actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK); else actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); } } catch { actionContext.Response = new System.Net.Http.HttpResponseMessage (System.Net.HttpStatusCode.InternalServerError); } } } We check if the authorization header is present; if not, an HTTP 401 or “unauthorized” response is returned. The next step is to validate the user credentials passed via the authorization request header from the client. Before we do that, we should know how the Web API is to be called from the client. For this, I’ve prepared a test method. The test method uses the HttpClient class to call the Web API. Note that the user names are converted to Base64 string format before they are passed. The test method is given below. [TestMethod] public void BasicAuthenticationTest() { string username = Convert.ToBase64String(Encoding.UTF8.GetBytes("joydip")); string password = Convert.ToBase64String(Encoding.UTF8.GetBytes("joydip123")); HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", username + ":" + password); var result = client.GetAsync(new Uri("http://localhost/IDG/api/default/")).Result; Assert.IsTrue(result.IsSuccessStatusCode); } As you can see in the above code snippet, the user credentials are passed using the authorization header. Now that the client is ready, let’s complete the implementation of the BasicAuthenicationFilter class. Inside the OnActionExecuting method we would need to parse the header value in this class and check if the credentials supplied from the client match. For now, let’s assume that the user name and the password has values of joydip and joydip123, respectively (they are hard-coded). Here’s the complete code of the BasicAuthenticationFilter class that incorporates the validation of the user credentials. public class BasicAuthenticationAttribute : System.Web.Http.Filters.ActionFilterAttribute { public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) { try { if (actionContext.Request.Headers.Authorization == null) { actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); } else { actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError); var httpRequestHeader = actionContext.Request.Headers.GetValues("Authorization").FirstOrDefault(); httpRequestHeader = httpRequestHeader.Substring("Authorization".Length); string[] httpRequestHeaderValues = httpRequestHeader.Split(':'); string username = Encoding.UTF8.GetString(Convert.FromBase64String(httpRequestHeaderValues[0])); string password = Encoding.UTF8.GetString(Convert.FromBase64String(httpRequestHeaderValues[1])); if (username.Equals("joydip") && password.Equals("joydip123")) actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK); else actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); } } catch { actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError); } } } In your controller class you should specify the attribute appropriately. Note that the BasicAuthentication attribute here refers to the BasicAuthenticationAttribute class we implemented. [BasicAuthentication] public class DefaultController : ApiController { public IEnumerable<string> Get() { return new string[] { "Joydip", "Kanjilal" }; } } Now, a bit of configuration — you need to configure the attribute so that calls to your controller would be filtered appropriately for the authentication to work. public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); config.Formatters.Remove(config.Formatters.XmlFormatter); GlobalConfiguration.Configuration.Filters.Add(new BasicAuthenticationAttribute()); } } And you are done! When you execute the test case, the test passes. You should anyway ensure that the credentials are not hard-coded; rather, they should be stored in a database and you should retrieve them and validate in the OnActionExecuting method of the BasicAuthenticationAttribute class. Related content feature 14 great preprocessors for developers who love to code Sometimes it seems like the rules of programming are designed to make coding a chore. Here are 14 ways preprocessors can help make software development fun again. By Peter Wayner Nov 18, 2024 10 mins Development Tools Software Development feature Designing the APIs that accidentally power businesses Well-designed APIs, even those often-neglected internal APIs, make developers more productive and businesses more agile. By Jean Yang Nov 18, 2024 6 mins APIs Software Development news Spin 3.0 supports polyglot development using Wasm components Fermyon’s open source framework for building server-side WebAssembly apps allows developers to compose apps from components created with different languages. By Paul Krill Nov 18, 2024 2 mins Microservices Serverless Computing Development Libraries and Frameworks news Go language evolving for future hardware, AI workloads The Go team is working to adapt Go to large multicore systems, the latest hardware instructions, and the needs of developers of large-scale AI systems. By Paul Krill Nov 15, 2024 3 mins Google Go Generative AI Programming Languages Resources Videos