├── omnisharp.json ├── Authorization ├── AllowAnonymousAttribute.cs ├── AuthorizeAttribute.cs └── BasicAuthMiddleware.cs ├── appsettings.json ├── README.md ├── WebApi.csproj ├── Models └── AuthenticateModel.cs ├── Entities └── User.cs ├── .gitignore ├── Program.cs ├── Controllers └── UsersController.cs ├── LICENSE └── Services └── UserService.cs /omnisharp.json: -------------------------------------------------------------------------------- 1 | { 2 | "msbuild": { 3 | "useBundledOnly": true 4 | } 5 | } -------------------------------------------------------------------------------- /Authorization/AllowAnonymousAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Authorization; 2 | 3 | [AttributeUsage(AttributeTargets.Method)] 4 | public class AllowAnonymousAttribute : Attribute 5 | { } -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dotnet-6-basic-authentication-api 2 | 3 | .NET 6.0 - Basic HTTP Authentication API 4 | 5 | Documentation at https://jasonwatmore.com/post/2021/12/20/net-6-basic-authentication-tutorial-with-example-api -------------------------------------------------------------------------------- /WebApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | enable 5 | 6 | -------------------------------------------------------------------------------- /Models/AuthenticateModel.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Models; 2 | 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | public class AuthenticateModel 6 | { 7 | [Required] 8 | public string Username { get; set; } 9 | 10 | [Required] 11 | public string Password { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /Entities/User.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Entities; 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | public class User 6 | { 7 | public int Id { get; set; } 8 | public string FirstName { get; set; } 9 | public string LastName { get; set; } 10 | public string Username { get; set; } 11 | 12 | [JsonIgnore] 13 | public string Password { get; set; } 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | typings 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | # .NET compiled files 41 | bin 42 | obj -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using WebApi.Authorization; 2 | using WebApi.Services; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | 6 | // add services to DI container 7 | { 8 | var services = builder.Services; 9 | services.AddCors(); 10 | services.AddControllers(); 11 | 12 | // configure DI for application services 13 | services.AddScoped(); 14 | } 15 | 16 | var app = builder.Build(); 17 | 18 | // configure HTTP request pipeline 19 | { 20 | // global cors policy 21 | app.UseCors(x => x 22 | .AllowAnyOrigin() 23 | .AllowAnyMethod() 24 | .AllowAnyHeader()); 25 | 26 | // custom basic auth middleware 27 | app.UseMiddleware(); 28 | 29 | app.MapControllers(); 30 | } 31 | 32 | app.Run("http://localhost:4000"); -------------------------------------------------------------------------------- /Controllers/UsersController.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Controllers; 2 | 3 | using Microsoft.AspNetCore.Mvc; 4 | using WebApi.Authorization; 5 | using WebApi.Models; 6 | using WebApi.Services; 7 | 8 | [Authorize] 9 | [ApiController] 10 | [Route("[controller]")] 11 | public class UsersController : ControllerBase 12 | { 13 | private IUserService _userService; 14 | 15 | public UsersController(IUserService userService) 16 | { 17 | _userService = userService; 18 | } 19 | 20 | [AllowAnonymous] 21 | [HttpPost("authenticate")] 22 | public async Task Authenticate([FromBody]AuthenticateModel model) 23 | { 24 | var user = await _userService.Authenticate(model.Username, model.Password); 25 | 26 | if (user == null) 27 | return BadRequest(new { message = "Username or password is incorrect" }); 28 | 29 | return Ok(user); 30 | } 31 | 32 | [HttpGet] 33 | public async Task GetAll() 34 | { 35 | var users = await _userService.GetAll(); 36 | return Ok(users); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Jason Watmore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Authorization/AuthorizeAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Authorization; 2 | 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.Filters; 5 | using WebApi.Entities; 6 | 7 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 8 | public class AuthorizeAttribute : Attribute, IAuthorizationFilter 9 | { 10 | public void OnAuthorization(AuthorizationFilterContext context) 11 | { 12 | // skip authorization if action is decorated with [AllowAnonymous] attribute 13 | var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType().Any(); 14 | if (allowAnonymous) 15 | return; 16 | 17 | var user = (User)context.HttpContext.Items["User"]; 18 | if (user == null) 19 | { 20 | // not logged in - return 401 unauthorized 21 | context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized }; 22 | 23 | // set 'WWW-Authenticate' header to trigger login popup in browsers 24 | context.HttpContext.Response.Headers["WWW-Authenticate"] = "Basic realm=\"\", charset=\"UTF-8\""; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Services/UserService.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Services; 2 | 3 | using WebApi.Entities; 4 | 5 | public interface IUserService 6 | { 7 | Task Authenticate(string username, string password); 8 | Task> GetAll(); 9 | } 10 | 11 | public class UserService : IUserService 12 | { 13 | // users hardcoded for simplicity, store in a db with hashed passwords in production applications 14 | private List _users = new List 15 | { 16 | new User { Id = 1, FirstName = "Test", LastName = "User", Username = "test", Password = "test" } 17 | }; 18 | 19 | public async Task Authenticate(string username, string password) 20 | { 21 | // wrapped in "await Task.Run" to mimic fetching user from a db 22 | var user = await Task.Run(() => _users.SingleOrDefault(x => x.Username == username && x.Password == password)); 23 | 24 | // on auth fail: null is returned because user is not found 25 | // on auth success: user object is returned 26 | return user; 27 | } 28 | 29 | public async Task> GetAll() 30 | { 31 | // wrapped in "await Task.Run" to mimic fetching users from a db 32 | return await Task.Run(() => _users); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Authorization/BasicAuthMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Authorization; 2 | 3 | using System.Net.Http.Headers; 4 | using System.Text; 5 | using WebApi.Services; 6 | 7 | public class BasicAuthMiddleware 8 | { 9 | private readonly RequestDelegate _next; 10 | 11 | public BasicAuthMiddleware(RequestDelegate next) 12 | { 13 | _next = next; 14 | } 15 | 16 | public async Task Invoke(HttpContext context, IUserService userService) 17 | { 18 | try 19 | { 20 | var authHeader = AuthenticationHeaderValue.Parse(context.Request.Headers["Authorization"]); 21 | var credentialBytes = Convert.FromBase64String(authHeader.Parameter); 22 | var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':', 2); 23 | var username = credentials[0]; 24 | var password = credentials[1]; 25 | 26 | // authenticate credentials with user service and attach user to http context 27 | context.Items["User"] = await userService.Authenticate(username, password); 28 | } 29 | catch 30 | { 31 | // do nothing if invalid auth header 32 | // user is not attached to context so request won't have access to secure routes 33 | } 34 | 35 | await _next(context); 36 | } 37 | } --------------------------------------------------------------------------------