├── 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 | }
--------------------------------------------------------------------------------