diff --git a/NDB.Security.Authentication.Identity/BasicAuthenticationExtensions.cs b/NDB.Security.Authentication.Identity/BasicAuthenticationExtensions.cs new file mode 100644 index 0000000..f61cf7d --- /dev/null +++ b/NDB.Security.Authentication.Identity/BasicAuthenticationExtensions.cs @@ -0,0 +1,25 @@ +using IdentityServer.Wrapper; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace NDB.Security.Authentication.Identity +{ + public static class BasicAuthenticationExtensions + { + public static IServiceCollection AddBasicAuthentication(this IServiceCollection services, string identityServerBaseAddress) + { + if (string.IsNullOrEmpty(identityServerBaseAddress)) + throw new Exception($"Identity server base address must be provided."); + + // Identity server + services.UseIdentityServices(identityServerBaseAddress); + + // configure basic authentication + services.AddAuthentication("BasicAuthentication") + .AddScheme("BasicAuthentication", null); + + return services; + } + } +} diff --git a/NDB.Security.Authentication.Identity/BasicAuthenticationHandler.cs b/NDB.Security.Authentication.Identity/BasicAuthenticationHandler.cs new file mode 100644 index 0000000..467ccc0 --- /dev/null +++ b/NDB.Security.Authentication.Identity/BasicAuthenticationHandler.cs @@ -0,0 +1,55 @@ +using IdentityServer.PublishedLanguage.Dto; +using IdentityServer.Wrapper.Services; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; + +namespace NDB.Security.Authentication.Identity +{ + public class BasicAuthenticationHandler : AuthenticationHandler + { + private readonly IIdentityService _identityService; + + public BasicAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IIdentityService identityService) + : base(options, logger, encoder, clock) + { + _identityService = identityService; + } + + protected override async Task HandleAuthenticateAsync() + { + if (!Request.Headers.ContainsKey("Authorization")) + return AuthenticateResult.Fail("Missing Authorization Header"); + + User user; + try + { + var authorizationHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]); + var token = authorizationHeader.Parameter; + user = await _identityService.Authorize(token); + } + catch + { + return AuthenticateResult.Fail("Invalid Authorization Header"); + } + + if (user == null) + return AuthenticateResult.Fail("Invalid Username or Password"); + + var claims = new[] { + new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString()), + new Claim(ClaimTypes.Name, user.UserName), + }; + + var identity = new ClaimsIdentity(claims, Scheme.Name); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, Scheme.Name); + + return AuthenticateResult.Success(ticket); + } + } +} diff --git a/NDB.Security.Authentication.Identity/NDB.Security.Authentication.Identity.csproj b/NDB.Security.Authentication.Identity/NDB.Security.Authentication.Identity.csproj new file mode 100644 index 0000000..8ce2bdb --- /dev/null +++ b/NDB.Security.Authentication.Identity/NDB.Security.Authentication.Identity.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + + + + + + + + diff --git a/NDB.sln b/NDB.sln index 4301157..4d3c9d7 100644 --- a/NDB.sln +++ b/NDB.sln @@ -36,7 +36,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NDB.Application.DataContrac EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "http", "http", "{C1301480-5C4C-4F73-8D26-DD3E798FAFD5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NDB.Extensions.Http", "NDB.Extensions.Http\NDB.Extensions.Http.csproj", "{28D5CE9E-D975-4842-8B30-5063B82979C6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NDB.Extensions.Http", "NDB.Extensions.Http\NDB.Extensions.Http.csproj", "{28D5CE9E-D975-4842-8B30-5063B82979C6}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "data", "data", "{7D49E538-B89A-4BC2-AD5A-5A658F4B74E4}" EndProject @@ -46,6 +46,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logging", "logging", "{06D5 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "infrastructure", "infrastructure", "{1C1D634E-06CC-4707-9564-E31A76F27D9E}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "security", "security", "{420A97AE-8E1E-4ECF-AAC5-455ABAA9B17E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "authentication", "authentication", "{B8132F39-6677-4D70-84CA-9747DC9086B3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NDB.Security.Authentication.Identity", "NDB.Security.Authentication.Identity\NDB.Security.Authentication.Identity.csproj", "{5C0637C8-6BA4-4EAE-97CA-BB8D98B2991A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -84,6 +90,10 @@ Global {28D5CE9E-D975-4842-8B30-5063B82979C6}.Debug|Any CPU.Build.0 = Debug|Any CPU {28D5CE9E-D975-4842-8B30-5063B82979C6}.Release|Any CPU.ActiveCfg = Release|Any CPU {28D5CE9E-D975-4842-8B30-5063B82979C6}.Release|Any CPU.Build.0 = Release|Any CPU + {5C0637C8-6BA4-4EAE-97CA-BB8D98B2991A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C0637C8-6BA4-4EAE-97CA-BB8D98B2991A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C0637C8-6BA4-4EAE-97CA-BB8D98B2991A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C0637C8-6BA4-4EAE-97CA-BB8D98B2991A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -104,6 +114,9 @@ Global {9F1BAC15-1625-40F7-9B7E-7C9CB6345DFF} = {E0202271-4E92-4DB8-900D-B5FD745B9278} {06D5F056-4099-4636-A45C-D6C3B2CCDD66} = {E0202271-4E92-4DB8-900D-B5FD745B9278} {1C1D634E-06CC-4707-9564-E31A76F27D9E} = {E0202271-4E92-4DB8-900D-B5FD745B9278} + {420A97AE-8E1E-4ECF-AAC5-455ABAA9B17E} = {E0202271-4E92-4DB8-900D-B5FD745B9278} + {B8132F39-6677-4D70-84CA-9747DC9086B3} = {420A97AE-8E1E-4ECF-AAC5-455ABAA9B17E} + {5C0637C8-6BA4-4EAE-97CA-BB8D98B2991A} = {B8132F39-6677-4D70-84CA-9747DC9086B3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87541BAB-3FAC-4ADB-A7FB-8228DA87843D}