Pros and cons?
So you want to secure your api or you mvc application? And you want to keep it really simple! Well then Basic Auth might be just right for you!
Before you get started with applying basic auth it's good to keep in mind that Basic Auth it comes with some disadvantages that could be considered deal-breaker, but knowing your weakness allows you to take actions and handle them.
Here I've listed some pros and cons for the basic auth protocol.
Pros
- Internet standard
- Supported by all major browsers.
- Relatively simple protocol.
Cons
- The credentials for the user are sent with each request.
- The credentials are sent as plaintext
- No way to log out, except by ending the session
- Could be vulnerable to cross-site request forgery (CSRF)
Solutions
There are a few ways to solve this problem, down below you can find three of them.
Attribute and filter solution
If you want the possibility to specify on a controller and/or method level the right choice for you could be
Example Code
BasicAuthorizeAttribute.cs
1// -------------------------------------------------------------------------------------------------2// Copyright (c) Johan Boström. All rights reserved.3// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.4// -------------------------------------------------------------------------------------------------56namespace API.BasicAuth.Attributes7{8 using System;9 using Microsoft.AspNetCore.Mvc;1011 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]12 public class BasicAuthorizeAttribute : TypeFilterAttribute13 {14 public BasicAuthorizeAttribute(string realm = null)15 : base(typeof(BasicAuthorizeFilter))16 {17 Arguments = new object[]18 {19 realm20 };21 }22 }23}
BasicAuthorizeFilter.cs
1// -------------------------------------------------------------------------------------------------2// Copyright (c) Johan Boström. All rights reserved.3// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.4// -------------------------------------------------------------------------------------------------56namespace API.BasicAuth.Attributes7{8 using System;9 using System.Text;10 using Microsoft.AspNetCore.Mvc;11 using Microsoft.AspNetCore.Mvc.Filters;1213 public class BasicAuthorizeFilter : IAuthorizationFilter14 {15 private readonly string realm;1617 public BasicAuthorizeFilter(string realm = null)18 {19 this.realm = realm;20 }2122 public void OnAuthorization(AuthorizationFilterContext context)23 {24 string authHeader = context.HttpContext.Request.Headers["Authorization"];25 if (authHeader != null && authHeader.StartsWith("Basic "))26 {27 // Get the encoded username and password28 var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();2930 // Decode from Base64 to string31 var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword));3233 // Split username and password34 var username = decodedUsernamePassword.Split(':', 2)[0];35 var password = decodedUsernamePassword.Split(':', 2)[1];3637 // Check if login is correct38 if (IsAuthorized(username, password))39 {40 return;41 }42 }4344 // Return authentication type (causes browser to show login dialog)45 context.HttpContext.Response.Headers["WWW-Authenticate"] = "Basic";4647 // Add realm if it is not null48 if (!string.IsNullOrWhiteSpace(realm))49 {50 context.HttpContext.Response.Headers["WWW-Authenticate"] += $" realm=\"{realm}\"";51 }5253 // Return unauthorized54 context.Result = new UnauthorizedResult();55 }5657 // Make your own implementation of this58 public bool IsAuthorized(string username, string password)59 {60 // Check that username and password are correct61 return username.Equals("User1", StringComparison.InvariantCultureIgnoreCase)62 && password.Equals("SecretPassword!");63 }64 }65}
Example usage
SecuredController.cs
1// -------------------------------------------------------------------------------------------------2// Copyright (c) Johan Boström. All rights reserved.3// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.4// -------------------------------------------------------------------------------------------------56namespace API.BasicAuth.Controllers7{8 using System.Collections.Generic;9 using Attributes;10 using Microsoft.AspNetCore.Mvc;1112 [BasicAuthorize("my-example-realm.com")]13 [Route("api/[controller]")]14 public class SecuredController : Controller15 {16 // GET api/secured17 [HttpGet]18 public IEnumerable<string> Get()19 {20 return new[] { "Secret 1", "Secret 2" };21 }22 }23}
Middleware solution
We have the possibility to write a middleware to secure the site with basic auth. In the sample below we are securing the entire site with basic auth but it could be rewritten (or mapped) to only effect certain endpoints.
Example Code
When created the middleware you can use it by adding app.UseMiddleware<BasicAuthMiddleware>("example-realm.com");
to your Startup.cs
.
BasicAuthMiddleware.cs
1// -------------------------------------------------------------------------------------------------2// Copyright (c) Johan Boström. All rights reserved.3// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.4// -------------------------------------------------------------------------------------------------56namespace API.BasicAuth.Middlewares7{8 using System;9 using System.Net;10 using System.Text;11 using System.Threading.Tasks;12 using Microsoft.AspNetCore.Http;13 using Microsoft.AspNetCore.Routing;1415 public class BasicAuthMiddleware16 {17 private readonly RequestDelegate next;18 private readonly string realm;1920 public BasicAuthMiddleware(RequestDelegate next, string realm)21 {22 this.next = next;23 this.realm = realm;24 }2526 public async Task Invoke(HttpContext context)27 {28 string authHeader = context.Request.Headers["Authorization"];29 if (authHeader != null && authHeader.StartsWith("Basic "))30 {31 // Get the encoded username and password32 var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();3334 // Decode from Base64 to string35 var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword));3637 // Split username and password38 var username = decodedUsernamePassword.Split(':', 2)[0];39 var password = decodedUsernamePassword.Split(':', 2)[1];4041 // Check if login is correct42 if (IsAuthorized(username, password))43 {44 await next.Invoke(context);45 return;46 }47 }4849 // Return authentication type (causes browser to show login dialog)50 context.Response.Headers["WWW-Authenticate"] = "Basic";5152 // Add realm if it is not null53 if (!string.IsNullOrWhiteSpace(realm))54 {55 context.Response.Headers["WWW-Authenticate"] += $" realm=\"{realm}\"";56 }5758 // Return unauthorized59 context.Response.StatusCode = (int) HttpStatusCode.Unauthorized;60 }6162 // Make your own implementation of this63 public bool IsAuthorized(string username, string password)64 {65 // Check that username and password are correct66 return username.Equals("User1", StringComparison.InvariantCultureIgnoreCase)67 && password.Equals("SecretPassword!");68 }69 }70}
Final words
Often my solution of choice is the Attribute/Filter solution since I think it's more flexible. But which ever you choose both will work.
You can also find the solutions in a project on GitHub, which you can find at github.com/zarxor/Example.API.Secured.
And remember, always ... ALWAYS (!) use HTTPS.