dotnet-6-basic-authentication-api/Entities/User.cs
namespace WebApi.Entities;
using System.Text.Json.Serialization;
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
[JsonIgnore]
public string Password { get; set; }
}
dotnet-6-basic-authentication-api/Services/UserService.cs
namespace WebApi.Services;
using WebApi.Entities;
public interface IUserService
{
Task<User> Authenticate(string username, string password);
Task<IEnumerable<User>> GetAll();
}
public class UserService : IUserService
{
// users hardcoded for simplicity, store in a db with hashed passwords in production applications
private List<User> _users = new List<User>
{
new User { Id = 1, FirstName = "Test", LastName = "User", Username = "test", Password = "test" }
};
public async Task<User> Authenticate(string username, string password)
{
// wrapped in "await Task.Run" to mimic fetching user from a db
var user = await Task.Run(() => _users.SingleOrDefault(x => x.Username == username && x.Password == password));
// on auth fail: null is returned because user is not found
// on auth success: user object is returned
return user;
}
public async Task<IEnumerable<User>> GetAll()
{
// wrapped in "await Task.Run" to mimic fetching users from a db
return await Task.Run(() => _users);
}
}
dotnet-6-basic-authentication-api/Controllers/UsersController.cs
namespace WebApi.Controllers;
using Microsoft.AspNetCore.Mvc;
using WebApi.Authorization;
using WebApi.Models;
using WebApi.Services;
[Authorize]
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
private IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[AllowAnonymous]
[HttpPost("authenticate")]
public async Task<IActionResult> Authenticate([FromBody]AuthenticateModel model)
{
var user = await _userService.Authenticate(model.Username, model.Password);
if (user == null)
return BadRequest(new { message = "Username or password is incorrect" });
return Ok(user);
}
[HttpGet]
public async Task<IActionResult> GetAll()
{
var users = await _userService.GetAll();
return Ok(users);
}
}
dotnet-6-basic-authentication-api/Controllers/CustomersController.cs
namespace WebApi.Controllers;
using Microsoft.AspNetCore.Mvc;
using WebApi.Authorization;
using WebApi.Models;
using WebApi.Services;
[ApiController]
[Route("[controller]")]
public class CustomersController : ControllerBase
{
private IUserService _userService;
public CustomersController(IUserService userService)
{
_userService = userService;
}
[HttpGet]
public IActionResult Get()
{
return Ok("this is customer controller");
}
}
dotnet-6-basic-authentication-api/Authorization/AllowAnonymousAttribute.cs
namespace WebApi.Authorization;
[AttributeUsage(AttributeTargets.Method)]
public class AllowAnonymousAttribute : Attribute
{
}
dotnet-6-basic-authentication-api/Authorization/AuthorizeAttribute.cs
namespace WebApi.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WebApi.Entities;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
// skip authorization if action is decorated with [AllowAnonymous] attribute
var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any();
if (allowAnonymous)
return;
var user = (User)context.HttpContext.Items["User"];
if (user == null)
{
// not logged in - return 401 unauthorized
context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
// set 'WWW-Authenticate' header to trigger login popup in browsers
context.HttpContext.Response.Headers["WWW-Authenticate"] = "Basic realm=\"\", charset=\"UTF-8\"";
}
}
}
dotnet-6-basic-authentication-api/Authorization/BasicAuthMiddleware.cs
namespace WebApi.Authorization;
using System.Net.Http.Headers;
using System.Text;
using WebApi.Services;
public class BasicAuthMiddleware
{
private readonly RequestDelegate _next;
public BasicAuthMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IUserService userService)
{
try
{
var authHeader = AuthenticationHeaderValue.Parse(context.Request.Headers["Authorization"]);
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':', 2);
var username = credentials[0];
var password = credentials[1];
// authenticate credentials with user service and attach user to http context
context.Items["User"] = await userService.Authenticate(username, password);
}
catch
{
// do nothing if invalid auth header
// user is not attached to context so request won't have access to secure routes
}
await _next(context);
}
}
dotnet-6-basic-authentication-api/Models/AuthenticateModel.cs
namespace WebApi.Models;
using System.ComponentModel.DataAnnotations;
public class AuthenticateModel
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}
dotnet-6-basic-authentication-api/Middlewares/LoggingMiddleware.cs
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
public LoggingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
Console.WriteLine($"Request path: {context.Request.Path}");
Console.WriteLine($"Request time: {DateTime.Now}");
await _next(context);
}
}
dotnet-6-basic-authentication-api/Program.cs
using WebApi.Authorization;
using WebApi.Services;
var builder = WebApplication.CreateBuilder(args);
// add services to DI container
{
var services = builder.Services;
services.AddCors();
services.AddControllers();
// configure DI for application services
services.AddScoped<IUserService, UserService>();
}
var app = builder.Build();
// configure HTTP request pipeline
{
// global cors policy
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
// custom basic auth middleware
app
.UseMiddleware<BasicAuthMiddleware>()
.UseMiddleware<LoggingMiddleware>();
app.MapControllers();
}
app.Run("http://localhost:4000");
dotnet-6-basic-authentication-api/global.json
{
"sdk": {
"version": "6.0.412"
}
}
dotnet-6-basic-authentication-api/.gitignore
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
typings
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# .NET compiled files
bin
obj
dotnet-6-basic-authentication-api/dotnet-6-basic-authentication-api.sln
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "WebApi.csproj", "{79F25DFB-5FDB-4E70-9946-E4A1D4CF290A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{79F25DFB-5FDB-4E70-9946-E4A1D4CF290A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79F25DFB-5FDB-4E70-9946-E4A1D4CF290A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79F25DFB-5FDB-4E70-9946-E4A1D4CF290A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79F25DFB-5FDB-4E70-9946-E4A1D4CF290A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {517F54C7-E702-4E85-B0E6-FD87AE3FAA41}
EndGlobalSection
EndGlobal
dotnet-6-basic-authentication-api/appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
dotnet-6-basic-authentication-api/WebApi.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
dotnet-6-basic-authentication-api/omnisharp.json
{
"msbuild": {
"useBundledOnly": true
}
}