Adding Basic Auth to your MVC application in .NET Core

Tutorial and examples on adding Basic Auth to your .NET Core application with attribute, filter and middleware solution.

TutorialsSecurity
Basic Auth .NET Core

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

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// -------------------------------------------------------------------------------------------------
5
6namespace API.BasicAuth.Attributes
7{
8 using System;
9 using Microsoft.AspNetCore.Mvc;
10
11 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
12 public class BasicAuthorizeAttribute : TypeFilterAttribute
13 {
14 public BasicAuthorizeAttribute(string realm = null)
15 : base(typeof(BasicAuthorizeFilter))
16 {
17 Arguments = new object[]
18 {
19 realm
20 };
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// -------------------------------------------------------------------------------------------------
5
6namespace API.BasicAuth.Attributes
7{
8 using System;
9 using System.Text;
10 using Microsoft.AspNetCore.Mvc;
11 using Microsoft.AspNetCore.Mvc.Filters;
12
13 public class BasicAuthorizeFilter : IAuthorizationFilter
14 {
15 private readonly string realm;
16
17 public BasicAuthorizeFilter(string realm = null)
18 {
19 this.realm = realm;
20 }
21
22 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 password
28 var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();
29
30 // Decode from Base64 to string
31 var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword));
32
33 // Split username and password
34 var username = decodedUsernamePassword.Split(':', 2)[0];
35 var password = decodedUsernamePassword.Split(':', 2)[1];
36
37 // Check if login is correct
38 if (IsAuthorized(username, password))
39 {
40 return;
41 }
42 }
43
44 // Return authentication type (causes browser to show login dialog)
45 context.HttpContext.Response.Headers["WWW-Authenticate"] = "Basic";
46
47 // Add realm if it is not null
48 if (!string.IsNullOrWhiteSpace(realm))
49 {
50 context.HttpContext.Response.Headers["WWW-Authenticate"] += $" realm=\"{realm}\"";
51 }
52
53 // Return unauthorized
54 context.Result = new UnauthorizedResult();
55 }
56
57 // Make your own implementation of this
58 public bool IsAuthorized(string username, string password)
59 {
60 // Check that username and password are correct
61 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// -------------------------------------------------------------------------------------------------
5
6namespace API.BasicAuth.Controllers
7{
8 using System.Collections.Generic;
9 using Attributes;
10 using Microsoft.AspNetCore.Mvc;
11
12 [BasicAuthorize("my-example-realm.com")]
13 [Route("api/[controller]")]
14 public class SecuredController : Controller
15 {
16 // GET api/secured
17 [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// -------------------------------------------------------------------------------------------------
5
6namespace API.BasicAuth.Middlewares
7{
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;
14
15 public class BasicAuthMiddleware
16 {
17 private readonly RequestDelegate next;
18 private readonly string realm;
19
20 public BasicAuthMiddleware(RequestDelegate next, string realm)
21 {
22 this.next = next;
23 this.realm = realm;
24 }
25
26 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 password
32 var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();
33
34 // Decode from Base64 to string
35 var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword));
36
37 // Split username and password
38 var username = decodedUsernamePassword.Split(':', 2)[0];
39 var password = decodedUsernamePassword.Split(':', 2)[1];
40
41 // Check if login is correct
42 if (IsAuthorized(username, password))
43 {
44 await next.Invoke(context);
45 return;
46 }
47 }
48
49 // Return authentication type (causes browser to show login dialog)
50 context.Response.Headers["WWW-Authenticate"] = "Basic";
51
52 // Add realm if it is not null
53 if (!string.IsNullOrWhiteSpace(realm))
54 {
55 context.Response.Headers["WWW-Authenticate"] += $" realm=\"{realm}\"";
56 }
57
58 // Return unauthorized
59 context.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
60 }
61
62 // Make your own implementation of this
63 public bool IsAuthorized(string username, string password)
64 {
65 // Check that username and password are correct
66 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.

Share to: