diff --git a/DotBased.AspNet.Authority/AuthorityDefaults.cs b/DotBased.AspNet.Authority/AuthorityDefaults.cs index d5b9b1f..43568c4 100755 --- a/DotBased.AspNet.Authority/AuthorityDefaults.cs +++ b/DotBased.AspNet.Authority/AuthorityDefaults.cs @@ -6,18 +6,18 @@ public static class AuthorityDefaults { public static class Authority { - public const string AuthenticationScheme = "Authority.Scheme.Password"; + public const string AuthenticationScheme = "AuthorityLogin"; } public static class Cookie { - public const string Default = "Authority.Scheme.Cookie"; + public const string AuthenticationScheme = "AuthorityCookie"; public const string CookieName = "AuthorityAuth"; } public static class Token { - public const string Default = "Authority.Scheme.Token"; + public const string AuthenticationScheme = "AuthorityToken"; public const string TokenName = "AuthorityAuthToken"; } } diff --git a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs index 88b3a44..16681d3 100755 --- a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs +++ b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs @@ -1,4 +1,5 @@ using DotBased.AspNet.Authority.Crypto; +using DotBased.AspNet.Authority.Handlers; using DotBased.AspNet.Authority.Managers; using DotBased.AspNet.Authority.Models.Options; using DotBased.AspNet.Authority.Models.Options.Auth; @@ -33,29 +34,41 @@ public static class AuthorityProviderExtensions return new AuthorityBuilder(services); } - public static AuthenticationBuilder AddAuthorityAuth(this AuthorityBuilder builder) => AddAuthorityAuth(builder, _ => { }); - public static AuthenticationBuilder AddAuthorityAuth(this AuthorityBuilder builder, Action configureOptions) { ArgumentNullException.ThrowIfNull(configureOptions); builder.Services.Configure(configureOptions); builder.Services.AddScoped(); - //TODO: Register authority default authentication handler - var authBuilder = builder.Services.AddAuthentication(); + var authorityOptions = new AuthorityAuthenticationOptions(); + configureOptions.Invoke(authorityOptions); + + var authBuilder = builder.Services.AddAuthentication(options => + { + options.DefaultScheme = authorityOptions.DefaultScheme; + options.DefaultAuthenticateScheme = authorityOptions.DefaultAuthenticateScheme; + options.DefaultChallengeScheme = authorityOptions.DefaultChallengeScheme; + options.DefaultSignInScheme = authorityOptions.DefaultSignInScheme; + options.DefaultSignOutScheme = authorityOptions.DefaultSignOutScheme; + options.DefaultForbidScheme = authorityOptions.DefaultForbidScheme; + }); return authBuilder; } - public static AuthenticationBuilder AddAuthorityLoginScheme(this AuthenticationBuilder builder, string scheme = AuthorityDefaults.Scheme.Authority.AuthenticationScheme) + public static AuthenticationBuilder AddAuthorityLoginScheme(this AuthenticationBuilder builder, string scheme) => + AddAuthorityLoginScheme(builder, scheme, _ => { }); + public static AuthenticationBuilder AddAuthorityLoginScheme(this AuthenticationBuilder builder, + string scheme, + Action configureOptions) { - + builder.AddScheme(scheme, scheme, configureOptions); return builder; } - public static AuthenticationBuilder AddAuthorityCookie(this AuthenticationBuilder builder, string scheme = AuthorityDefaults.Scheme.Cookie.Default) + public static AuthenticationBuilder AddAuthorityCookie(this AuthenticationBuilder builder, string scheme) { - builder.AddCookie(options => + builder.AddCookie(scheme, options => { options.Cookie.Name = AuthorityDefaults.Scheme.Cookie.CookieName; options.Cookie.Path = AuthorityDefaults.Paths.Default; @@ -71,7 +84,7 @@ public static class AuthorityProviderExtensions return builder; } - public static AuthenticationBuilder AddAuthorityToken(this AuthenticationBuilder builder, string scheme = AuthorityDefaults.Scheme.Token.Default) + public static AuthenticationBuilder AddAuthorityToken(this AuthenticationBuilder builder, string scheme) { return builder; diff --git a/DotBased.AspNet.Authority/Controllers/AuthorityController.cs b/DotBased.AspNet.Authority/Controllers/AuthorityController.cs index 1568196..36ec5c2 100644 --- a/DotBased.AspNet.Authority/Controllers/AuthorityController.cs +++ b/DotBased.AspNet.Authority/Controllers/AuthorityController.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using Microsoft.AspNetCore.Mvc; namespace DotBased.AspNet.Authority.Controllers; @@ -6,5 +7,21 @@ namespace DotBased.AspNet.Authority.Controllers; [Route("[controller]")] public class AuthorityController : ControllerBase { - + [HttpGet("auth/login")] + public async Task LoginFromSchemeAsync([FromQuery(Name = "s")] string scheme) + { + return BadRequest(); + } + + [HttpGet("auth/logout")] + public async Task LogoutAsync() + { + return Ok(); + } + + [HttpGet("info")] + public async Task> GetAuthorityInfoAsync() + { + return Ok(); + } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj index 332f6b0..4a63eea 100755 --- a/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj +++ b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj @@ -7,9 +7,7 @@ - - ..\..\..\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\8.0.2\lib\net8.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - + @@ -22,10 +20,7 @@ - - - - + diff --git a/DotBased.AspNet.Authority/Handlers/AuthorityAuthenticationHandler.cs b/DotBased.AspNet.Authority/Handlers/AuthorityAuthenticationHandler.cs deleted file mode 100644 index f6c9992..0000000 --- a/DotBased.AspNet.Authority/Handlers/AuthorityAuthenticationHandler.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; - -namespace DotBased.AspNet.Authority.Handlers; - -/// -/// Handles authentication for Authority logins. -/// -public class AuthorityAuthenticationHandler : IAuthenticationSignInHandler -{ - public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) - { - throw new NotImplementedException(); - } - - public Task AuthenticateAsync() - { - throw new NotImplementedException(); - } - - public Task ChallengeAsync(AuthenticationProperties properties) - { - throw new NotImplementedException(); - } - - public Task ForbidAsync(AuthenticationProperties properties) - { - throw new NotImplementedException(); - } - - public Task SignOutAsync(AuthenticationProperties properties) - { - throw new NotImplementedException(); - } - - public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Handlers/AuthorityLoginAuthenticationHandler.cs b/DotBased.AspNet.Authority/Handlers/AuthorityLoginAuthenticationHandler.cs new file mode 100644 index 0000000..1efd59d --- /dev/null +++ b/DotBased.AspNet.Authority/Handlers/AuthorityLoginAuthenticationHandler.cs @@ -0,0 +1,34 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using DotBased.AspNet.Authority.Managers; +using DotBased.AspNet.Authority.Models.Options.Auth; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace DotBased.AspNet.Authority.Handlers; + +/// +/// Handles authentication for Authority logins. +/// +public class AuthorityLoginAuthenticationHandler(IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock, + AuthorityManager manager) : SignInAuthenticationHandler(options, logger, encoder, clock) +{ + protected override Task HandleAuthenticateAsync() + { + throw new NotImplementedException(); + } + + protected override Task HandleSignOutAsync(AuthenticationProperties properties) + { + throw new NotImplementedException(); + } + + protected override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Options/Auth/AuthorityAuthenticationOptions.cs b/DotBased.AspNet.Authority/Models/Options/Auth/AuthorityAuthenticationOptions.cs index 9f2a4f4..297216d 100644 --- a/DotBased.AspNet.Authority/Models/Options/Auth/AuthorityAuthenticationOptions.cs +++ b/DotBased.AspNet.Authority/Models/Options/Auth/AuthorityAuthenticationOptions.cs @@ -10,13 +10,16 @@ public class AuthorityAuthenticationOptions public string DefaultForbidScheme { get; set; } = string.Empty; public string DefaultSignInScheme { get; set; } = string.Empty; public string DefaultSignOutScheme { get; set; } = string.Empty; - public List SchemeMap { get; set; } = []; + /// + /// List of schemes that the Authority application will support to authenticate against. + /// + public List SchemeInfoMap { get; set; } = []; } public class SchemeInfo { public string Scheme { get; set; } = string.Empty; - public string Identifier { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; public SchemeType Type { get; set; } public string AuthenticationType { get; set; } = string.Empty; } diff --git a/DotBased.AspNet.Authority/Models/Options/Auth/AuthorityLoginOptions.cs b/DotBased.AspNet.Authority/Models/Options/Auth/AuthorityLoginOptions.cs new file mode 100644 index 0000000..a26e7b4 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Options/Auth/AuthorityLoginOptions.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Authentication; + +namespace DotBased.AspNet.Authority.Models.Options.Auth; + +public class AuthorityLoginOptions : AuthenticationSchemeOptions +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Services/AuthorityAuthenticationService.cs b/DotBased.AspNet.Authority/Services/AuthorityAuthenticationService.cs index b9556b1..e199439 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityAuthenticationService.cs +++ b/DotBased.AspNet.Authority/Services/AuthorityAuthenticationService.cs @@ -1,101 +1,20 @@ -using System.Security.Claims; using DotBased.AspNet.Authority.Models.Options.Auth; using DotBased.Logging; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; namespace DotBased.AspNet.Authority.Services; public class AuthorityAuthenticationService( + IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform, - IOptions options) : IAuthenticationService + IOptions options, + IOptions authorityOptions) : AuthenticationService(schemes, handlers, transform, options) { private readonly ILogger _logger = LogService.RegisterLogger(typeof(AuthorityAuthenticationService)); - private readonly AuthorityAuthenticationOptions _options = options.Value; + private readonly AuthorityAuthenticationOptions _options = authorityOptions.Value; - public async Task AuthenticateAsync(HttpContext context, string scheme) - { - _logger.Debug("Authenticate with scheme: {Scheme}", scheme); - - var authenticationHandler = await GetAuthenticationHandler(context, scheme, _options.DefaultAuthenticateScheme); - var authResult = await authenticationHandler.AuthenticateAsync(); - - return authResult is { Succeeded: true } - ? AuthenticateResult.Success( - new AuthenticationTicket(await transform.TransformAsync(authResult.Principal), authResult.Properties, authResult.Ticket.AuthenticationScheme)) : - AuthenticateResult.Fail("Failed to authenticate"); - } - - public async Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties) - { - _logger.Debug("Challenging with scheme: {Scheme}", scheme); - - var authenticationHandler = await GetAuthenticationHandler(context, scheme, _options.DefaultChallengeScheme); - await authenticationHandler.ChallengeAsync(properties); - } - - public async Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties) - { - _logger.Debug("Forbid with scheme: {Scheme}", scheme); - - var authenticationHandler = await GetAuthenticationHandler(context, scheme, _options.DefaultForbidScheme); - await authenticationHandler.ForbidAsync(properties); - } - - public async Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) - { - _logger.Debug("SignIn with scheme: {Scheme}", scheme); - - var authenticationHandler = await GetAuthenticationHandler(context, scheme, _options.DefaultSignInScheme); - if (authenticationHandler is not IAuthenticationSignInHandler signInHandler) - { - throw new InvalidOperationException("Authentication handler is not a IAuthenticationSignInHandler."); - } - await signInHandler.SignInAsync(principal, properties); - } - - public async Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties) - { - _logger.Debug("SignOut with scheme: {Scheme}", scheme); - - var authenticationHandler = await GetAuthenticationHandler(context, scheme, _options.DefaultSignOutScheme); - if (authenticationHandler is not IAuthenticationSignOutHandler signOutHandler) - { - throw new InvalidOperationException("Authentication handler is not a IAuthenticationSignOutHandler."); - } - await signOutHandler.SignOutAsync(properties); - } - - /*public async Task ValidateLoginAsync() - { - //TODO: Check if user is logged in from external identity provider, if user not exists in authority db create user. - throw new NotImplementedException(); - }*/ - - private async Task GetAuthenticationHandler(HttpContext context, string scheme, string defaultScheme) - { - if (string.IsNullOrWhiteSpace(scheme)) - { - scheme = defaultScheme; - if (string.IsNullOrWhiteSpace(scheme)) - { - scheme = _options.DefaultScheme; - if (string.IsNullOrWhiteSpace(scheme)) - { - throw new InvalidOperationException("Failed to get default scheme. No scheme specified."); - } - } - } - - var authenticationHandler = await handlers.GetHandlerAsync(context, scheme); - if (authenticationHandler == null) - { - _logger.Warning("Failed to load handler for scheme: {Scheme}", scheme); - throw new InvalidOperationException($"No authentication handlers registered to the scheme '{scheme}'"); - } - - return authenticationHandler; - } + public IReadOnlyCollection GetSchemeInfos(SchemeType schemeType) => _options.SchemeInfoMap.Where(s => s.Type == schemeType).ToList(); + public IReadOnlyCollection GetAllSchemeInfos() => _options.SchemeInfoMap; } \ No newline at end of file diff --git a/TestWebApi/Controllers/WeatherController.cs b/TestWebApi/Controllers/WeatherController.cs index 8df836d..375063b 100644 --- a/TestWebApi/Controllers/WeatherController.cs +++ b/TestWebApi/Controllers/WeatherController.cs @@ -27,4 +27,9 @@ public class WeatherController : ControllerBase await Task.Delay(TimeSpan.FromSeconds(1)); return forecast; } + + public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) + { + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } } \ No newline at end of file diff --git a/TestWebApi/Program.cs b/TestWebApi/Program.cs index f8c586b..aa3c731 100755 --- a/TestWebApi/Program.cs +++ b/TestWebApi/Program.cs @@ -25,45 +25,49 @@ builder.Logging.AddDotBasedLoggerProvider(LogService.Options); builder.Services.AddControllers(); -builder.Services.AddAuthority().AddAuthorityContext(options => +builder.Services.AddAuthority() +.AddAuthorityContext(options => { options.UseSqlite("Data Source=dev-authority.db", c => c.MigrationsAssembly("TestWebApi")); -}).AddAuthorityAuth(options => +}) +.MapAuthorityEndpoints() +.AddAuthorityAuth(options => { - options.DefaultScheme = AuthorityDefaults.Scheme.Authority.AuthenticationScheme; - //TODO: Auto detect auth and session store schemes? - options.SchemeMap = [ + options.DefaultScheme = AuthorityDefaults.Scheme.Cookie.AuthenticationScheme; + options.DefaultSignInScheme = AuthorityDefaults.Scheme.Authority.AuthenticationScheme; + options.DefaultChallengeScheme = AuthorityDefaults.Scheme.Authority.AuthenticationScheme; + options.SchemeInfoMap = [ new SchemeInfo { Scheme = AuthorityDefaults.Scheme.Authority.AuthenticationScheme, - Identifier = "Authority password login", + Description = "Authority password login", Type = SchemeType.Authentication, AuthenticationType = "Password" }, - new SchemeInfo + /*new SchemeInfo { Scheme = "OIDC", - Identifier = "Authentik OIDC login", + Description = "Authentik OIDC login", Type = SchemeType.Authentication, AuthenticationType = "OpenIdConnect" - }, + },*/ new SchemeInfo { - Scheme = AuthorityDefaults.Scheme.Cookie.Default, - Identifier = "Cookie session", + Scheme = AuthorityDefaults.Scheme.Cookie.AuthenticationScheme, + Description = "Cookie session", Type = SchemeType.SessionStore - }, + }/*, new SchemeInfo { - Scheme = AuthorityDefaults.Scheme.Token.Default, - Identifier = "Session token", + Scheme = AuthorityDefaults.Scheme.Token.AuthenticationScheme, + Description = "Session token", Type = SchemeType.SessionStore - } + }*/ ]; }) -.AddAuthorityLoginScheme() -.AddAuthorityCookie() -.AddAuthorityToken(); +.AddAuthorityLoginScheme(AuthorityDefaults.Scheme.Authority.AuthenticationScheme) +.AddAuthorityCookie(AuthorityDefaults.Scheme.Cookie.AuthenticationScheme) +.AddAuthorityToken(AuthorityDefaults.Scheme.Token.AuthenticationScheme); // Add services to the container. // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle @@ -96,9 +100,4 @@ ILogger SetupSerilog() .MinimumLevel.Verbose() .WriteTo.Console(outputTemplate: BasedSerilog.OutputTemplate); return logConfig.CreateLogger(); -} - -public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) -{ - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); } \ No newline at end of file