├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Controllers └── UsersController.cs ├── Entities └── User.cs ├── Helpers └── BasicAuthenticationHandler.cs ├── LICENSE ├── Models └── AuthenticateModel.cs ├── Program.cs ├── README.md ├── Services └── UserService.cs ├── Startup.cs ├── WebApi.csproj ├── appsettings.Development.json ├── appsettings.json └── omnisharp.json /.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 -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/net5.0/WebApi.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "env": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | }, 21 | "sourceFileMap": { 22 | "/Views": "${workspaceFolder}/Views" 23 | } 24 | }, 25 | { 26 | "name": ".NET Core Attach", 27 | "type": "coreclr", 28 | "request": "attach", 29 | "processId": "${command:pickProcess}" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/WebApi.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /Controllers/UsersController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Authorization; 3 | using WebApi.Services; 4 | using System.Threading.Tasks; 5 | using WebApi.Models; 6 | 7 | namespace WebApi.Controllers 8 | { 9 | [Authorize] 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class UsersController : ControllerBase 13 | { 14 | private IUserService _userService; 15 | 16 | public UsersController(IUserService userService) 17 | { 18 | _userService = userService; 19 | } 20 | 21 | [AllowAnonymous] 22 | [HttpPost("authenticate")] 23 | public async Task Authenticate([FromBody]AuthenticateModel model) 24 | { 25 | var user = await _userService.Authenticate(model.Username, model.Password); 26 | 27 | if (user == null) 28 | return BadRequest(new { message = "Username or password is incorrect" }); 29 | 30 | return Ok(user); 31 | } 32 | 33 | [HttpGet] 34 | public async Task GetAll() 35 | { 36 | var users = await _userService.GetAll(); 37 | return Ok(users); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Entities/User.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace WebApi.Entities 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 | } 15 | } -------------------------------------------------------------------------------- /Helpers/BasicAuthenticationHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http.Headers; 3 | using System.Security.Claims; 4 | using System.Text; 5 | using System.Text.Encodings.Web; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Authentication; 8 | using Microsoft.AspNetCore.Authorization; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | using WebApi.Entities; 13 | using WebApi.Services; 14 | 15 | namespace WebApi.Helpers 16 | { 17 | public class BasicAuthenticationHandler : AuthenticationHandler 18 | { 19 | private readonly IUserService _userService; 20 | 21 | public BasicAuthenticationHandler( 22 | IOptionsMonitor options, 23 | ILoggerFactory logger, 24 | UrlEncoder encoder, 25 | ISystemClock clock, 26 | IUserService userService) 27 | : base(options, logger, encoder, clock) 28 | { 29 | _userService = userService; 30 | } 31 | 32 | protected override async Task HandleAuthenticateAsync() 33 | { 34 | // skip authentication if endpoint has [AllowAnonymous] attribute 35 | var endpoint = Context.GetEndpoint(); 36 | if (endpoint?.Metadata?.GetMetadata() != null) 37 | return AuthenticateResult.NoResult(); 38 | 39 | if (!Request.Headers.ContainsKey("Authorization")) 40 | return AuthenticateResult.Fail("Missing Authorization Header"); 41 | 42 | User user = null; 43 | try 44 | { 45 | var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]); 46 | var credentialBytes = Convert.FromBase64String(authHeader.Parameter); 47 | var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2); 48 | var username = credentials[0]; 49 | var password = credentials[1]; 50 | user = await _userService.Authenticate(username, password); 51 | } 52 | catch 53 | { 54 | return AuthenticateResult.Fail("Invalid Authorization Header"); 55 | } 56 | 57 | if (user == null) 58 | return AuthenticateResult.Fail("Invalid Username or Password"); 59 | 60 | var claims = new[] { 61 | new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), 62 | new Claim(ClaimTypes.Name, user.Username), 63 | }; 64 | var identity = new ClaimsIdentity(claims, Scheme.Name); 65 | var principal = new ClaimsPrincipal(identity); 66 | var ticket = new AuthenticationTicket(principal, Scheme.Name); 67 | 68 | return AuthenticateResult.Success(ticket); 69 | } 70 | 71 | protected override Task HandleChallengeAsync(AuthenticationProperties properties) 72 | { 73 | Response.Headers["WWW-Authenticate"] = "Basic realm=\"\", charset=\"UTF-8\""; 74 | return base.HandleChallengeAsync(properties); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Models/AuthenticateModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace WebApi.Models 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 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace WebApi 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup() 18 | .UseUrls("http://localhost:4000"); 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dotnet-5-basic-authentication-api 2 | 3 | .NET 5.0 - Basic HTTP Authentication API 4 | 5 | For documentation and instructions check out https://jasonwatmore.com/post/2021/05/19/net-5-basic-authentication-tutorial-with-example-api -------------------------------------------------------------------------------- /Services/UserService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using WebApi.Entities; 5 | 6 | namespace WebApi.Services 7 | { 8 | public interface IUserService 9 | { 10 | Task Authenticate(string username, string password); 11 | Task> GetAll(); 12 | } 13 | 14 | public class UserService : IUserService 15 | { 16 | // users hardcoded for simplicity, store in a db with hashed passwords in production applications 17 | private List _users = new List 18 | { 19 | new User { Id = 1, FirstName = "Test", LastName = "User", Username = "test", Password = "test" } 20 | }; 21 | 22 | public async Task Authenticate(string username, string password) 23 | { 24 | // wrapped in "await Task.Run" to mimic fetching user from a db 25 | var user = await Task.Run(() => _users.SingleOrDefault(x => x.Username == username && x.Password == password)); 26 | 27 | // return null if user not found 28 | if (user == null) 29 | return null; 30 | 31 | // authentication successful so return user details 32 | return user; 33 | } 34 | 35 | public async Task> GetAll() 36 | { 37 | // wrapped in "await Task.Run" to mimic fetching users from a db 38 | return await Task.Run(() => _users); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using WebApi.Helpers; 6 | using WebApi.Services; 7 | using Microsoft.AspNetCore.Authentication; 8 | 9 | namespace WebApi 10 | { 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | 20 | // This method gets called by the runtime. Use this method to add services to the container. 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | services.AddCors(); 24 | services.AddControllers(); 25 | 26 | // configure basic authentication 27 | services.AddAuthentication("BasicAuthentication") 28 | .AddScheme("BasicAuthentication", null); 29 | 30 | // configure DI for application services 31 | services.AddScoped(); 32 | } 33 | 34 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 35 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 36 | { 37 | app.UseRouting(); 38 | 39 | // global cors policy 40 | app.UseCors(x => x 41 | .AllowAnyOrigin() 42 | .AllowAnyMethod() 43 | .AllowAnyHeader()); 44 | 45 | app.UseAuthentication(); 46 | app.UseAuthorization(); 47 | 48 | app.UseEndpoints(endpoints => endpoints.MapControllers()); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /WebApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net5.0 4 | 5 | -------------------------------------------------------------------------------- /appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /omnisharp.json: -------------------------------------------------------------------------------- 1 | { 2 | "msbuild": { 3 | "useBundledOnly": true 4 | } 5 | } --------------------------------------------------------------------------------