Compare commits

...

5 Commits

Author SHA1 Message Date
Developer 02
0a61586e39 refactor(ConsumerApi): umbenannt in Consumer mit LoginDto.
- ConsumerApi.Name umbenannt in Id
 - Eigenschaft audience hinzugefügt.
2025-01-21 17:13:53 +01:00
Developer 02
47aeb49a40 feat(ClaimExtensions): Ermöglicht die Bereitstellung von Methoden zum Abrufen spezifischer Ansprüche. 2025-01-21 16:38:09 +01:00
Developer 02
a1f996b328 feat: DependentExtensions für extensions mit Abhängigkeiten hinzugefügt
- Methoden `AddDependentExtensions` und `TryGetByRoute` hinzugefügt, um die Konfiguration von `AuthApiParams` und das Abrufen von Deskriptoren zu ermöglichen.
2025-01-21 15:23:22 +01:00
Developer 02
110b102926 refactor(Verbraucher): Als Unterklasse zur AuthApiParams-Klasse verschoben, um die Komplexität zu reduzieren 2025-01-21 14:17:50 +01:00
Developer 02
ddc55e0fd9 fix: Um das Problem der Abhängigkeit von Microsoft.IdentityModel.Tokens zu lösen, wurde System.IdentityModel.Tokens.Jwt.
- Aktualisierte benötigte Pakete
2025-01-21 13:21:51 +01:00
19 changed files with 156 additions and 94 deletions

View File

@@ -28,5 +28,20 @@
public required string Issuer { get; init; }
public bool RequireHttpsMetadata { get; init; } = true;
public class Consumer
{
public required string Route { get; init; }
public required string Audience { get; init; }
private CookieOptionsProvider? _cookieOptions;
#pragma warning disable CS8603 // Possible null reference return.
public CookieOptionsProvider CookieOptions { get => _cookieOptions ?? Parent?.DefaultCookieOptions; init => _cookieOptions = value; }
#pragma warning restore CS8603 // Possible null reference return.
internal AuthApiParams? Parent { private get; set; }
}
}
}

View File

@@ -1,4 +1,6 @@
namespace DigitalData.Auth.API.Config
using static DigitalData.Auth.API.Config.AuthApiParams;
namespace DigitalData.Auth.API.Config
{
public static class ConfigExtensions
{

View File

@@ -1,17 +0,0 @@
namespace DigitalData.Auth.API.Config
{
public class Consumer
{
public required string Route { get; init; }
public required string Audience { get; init; }
private CookieOptionsProvider? _cookieOptions;
public CookieOptionsProvider CookieOptions { get => _cookieOptions ?? Parent?.DefaultCookieOptions; init => _cookieOptions = value; }
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public AuthApiParams Parent { private get; set; }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
}
}

View File

@@ -0,0 +1,39 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.Extensions.Options;
namespace DigitalData.Auth.API.Config
{
public static class DependentExtensions
{
private static AuthApiParams? _authApiParams;
private static AuthApiParams AuthApiParams
{
get => _authApiParams
?? throw new InvalidOperationException(
$"DependentExtensions have not been added to the application or are not configured correctly. {typeof(AuthApiParams)} cannot be provided."
);
set => _authApiParams = value;
}
public static IApplicationBuilder AddDependentExtensions(this IApplicationBuilder application)
{
var authApiParamOptions = application.ApplicationServices.GetRequiredService<IOptions<AuthApiParams>>();
_authApiParams = authApiParamOptions.Value;
return application;
}
public static bool TryGetByRoute(this IEnumerable<IAsymmetricTokenDescriptor> descriptors, string consumerRoute, out IAsymmetricTokenDescriptor descriptor)
{
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
descriptor = null;
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
if (!AuthApiParams.Consumers.TryGetByRoute(consumerRoute, out var consumer)
|| !descriptors.TryGet(AuthApiParams.Issuer, consumer.Audience, out var _descriptor))
return false;
descriptor = _descriptor;
return true;
}
}
}

View File

@@ -19,7 +19,7 @@ namespace DigitalData.Auth.API.Controllers
{
private readonly IJwtSignatureHandler<UserReadDto> _userSignatureHandler;
private readonly IJwtSignatureHandler<ConsumerApi> _apiSignatureHandler;
private readonly IJwtSignatureHandler<Consumer> _consumerSignatureHandler;
private readonly AuthApiParams _apiParams;
@@ -31,9 +31,9 @@ namespace DigitalData.Auth.API.Controllers
private readonly IDirectorySearchService _dirSearchService;
private readonly IConsumerApiService _consumerApiService;
private readonly IConsumerService _consumerService;
public AuthController(IJwtSignatureHandler<UserReadDto> userSignatureHandler, IOptions<AuthApiParams> cookieParamsOptions, ICryptoFactory cryptoFactory, ILogger<AuthController> logger, IUserService userService, IDirectorySearchService dirSearchService, IConsumerApiService consumerApiService, IJwtSignatureHandler<ConsumerApi> apiSignatureHandler)
public AuthController(IJwtSignatureHandler<UserReadDto> userSignatureHandler, IOptions<AuthApiParams> cookieParamsOptions, ICryptoFactory cryptoFactory, ILogger<AuthController> logger, IUserService userService, IDirectorySearchService dirSearchService, IConsumerService consumerService, IJwtSignatureHandler<Consumer> apiSignatureHandler)
{
_apiParams = cookieParamsOptions.Value;
_userSignatureHandler = userSignatureHandler;
@@ -41,8 +41,8 @@ namespace DigitalData.Auth.API.Controllers
_logger = logger;
_userService = userService;
_dirSearchService = dirSearchService;
_consumerApiService = consumerApiService;
_apiSignatureHandler = apiSignatureHandler;
_consumerService = consumerService;
_consumerSignatureHandler = apiSignatureHandler;
}
private async Task<IActionResult> CreateTokenAsync(LogInDto login, string consumerRoute, bool cookie = true)
@@ -72,9 +72,9 @@ namespace DigitalData.Auth.API.Controllers
return Ok(token);
}
private async Task<IActionResult> CreateTokenAsync(ConsumerApiLogin login, bool cookie = true)
private async Task<IActionResult> CreateTokenAsync(ConsumerLogin login, bool cookie = true)
{
var api = await _consumerApiService.ReadByNameAsync(login.Name);
var api = await _consumerService.ReadByIdAsync(login.Id);
if (api is null || api.Password != login.Password)
return Unauthorized();
@@ -82,7 +82,7 @@ namespace DigitalData.Auth.API.Controllers
if (!_cryptoFactory.TokenDescriptors.TryGet(_apiParams.Issuer, _apiParams.DefaultConsumer.Audience, out var descriptor))
return StatusCode(StatusCodes.Status500InternalServerError);
var token = _apiSignatureHandler.WriteToken(api, descriptor);
var token = _consumerSignatureHandler.WriteToken(api, descriptor);
//set cookie
if (cookie)
@@ -112,7 +112,7 @@ namespace DigitalData.Auth.API.Controllers
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromForm] ConsumerApiLogin login)
public async Task<IActionResult> Login([FromForm] ConsumerLogin login)
{
try
{
@@ -155,7 +155,7 @@ namespace DigitalData.Auth.API.Controllers
}
[HttpPost]
public async Task<IActionResult> CreateTokenViaBody([FromBody] ConsumerApiLogin login, [FromQuery] bool cookie = false)
public async Task<IActionResult> CreateTokenViaBody([FromBody] ConsumerLogin login, [FromQuery] bool cookie = false)
{
try
{

View File

@@ -0,0 +1,17 @@
using System.Security.Claims;
namespace DigitalData.Auth.API.Controllers
{
public static class ClaimExtensions
{
public static string? GetName(this ClaimsPrincipal user) => user.FindFirstValue(ClaimTypes.NameIdentifier);
public static bool TryGetName(this ClaimsPrincipal user, out string name)
{
#pragma warning disable CS8601 // Possible null reference assignment.
name = user.GetName();
#pragma warning restore CS8601 // Possible null reference assignment.
return name is not null;
}
}
}

View File

@@ -14,11 +14,12 @@
<PackageReference Include="DigitalData.Core.Application" Version="3.2.0" />
<PackageReference Include="DigitalData.Core.Security" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.12" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
<PackageReference Include="UserManager.Application" Version="3.1.2" />
<PackageReference Include="UserManager.Domain" Version="3.0.1" />
<PackageReference Include="UserManager.Infrastructure" Version="3.0.1" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,4 +0,0 @@
namespace DigitalData.Auth.API.Dto
{
public record ConsumerApiLogin(string Name, string Password);
}

View File

@@ -0,0 +1,4 @@
namespace DigitalData.Auth.API.Dto
{
public record ConsumerLogin(string Id, string Password);
}

View File

@@ -0,0 +1,4 @@
namespace DigitalData.Auth.API.Entities
{
public record Consumer(string Id, string Password, string Audience);
}

View File

@@ -1,4 +0,0 @@
namespace DigitalData.Auth.API.Entities
{
public record ConsumerApi(string Name, string Password);
}

View File

@@ -6,16 +6,14 @@ using DigitalData.Core.Application;
using DigitalData.Core.Security;
using DigitalData.UserManager.Application;
using DigitalData.UserManager.Application.DTOs.User;
using DigitalData.UserManager.Application.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("consumers-api.json", true, true);
builder.Configuration.AddJsonFile("consumer-repository.json", true, true);
var config = builder.Configuration;
@@ -23,11 +21,11 @@ var apiParams = config.Get<AuthApiParams>() ?? throw new InvalidOperationExcepti
// Add services to the container.
builder.Services.Configure<AuthApiParams>(config);
builder.Services.AddConsumerApiServiceFromConfiguration(config);
builder.Services.AddConsumerApiService(config);
builder.Services.AddCryptoFactory(config.GetSection("CryptParams"));
builder.Services.AddJwtSignatureHandler<ConsumerApi>(api => new Dictionary<string, object>
builder.Services.AddJwtSignatureHandler<Consumer>(api => new Dictionary<string, object>
{
{ JwtRegisteredClaimNames.Sub, api.Name },
{ JwtRegisteredClaimNames.Sub, api.Id },
{ JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds() }
});
builder.Services.AddJwtSignatureHandler<UserReadDto>(user => new Dictionary<string, object>
@@ -95,9 +93,7 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
ValidateAudience = true,
ValidAudience = apiParams!.DefaultConsumer.Audience,
ValidateLifetime = true,
IssuerSigningKey = issuerSigningKeyInitiator?.Value,
NameClaimType = JwtRegisteredClaimNames.Name,
RoleClaimType = ClaimTypes.Role
IssuerSigningKey = issuerSigningKeyInitiator?.Value
};
options.Events = new JwtBearerEvents
@@ -116,6 +112,8 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
var app = builder.Build();
app.AddDependentExtensions();
issuerSigningKeyInitiator = new Lazy<SecurityKey>(() =>
{
var factory = app.Services.GetRequiredService<ICryptoFactory>();

View File

@@ -1,19 +0,0 @@
using DigitalData.Auth.API.Entities;
using DigitalData.Auth.API.Services.Contracts;
using Microsoft.Extensions.Options;
namespace DigitalData.Auth.API.Services
{
public class ConfiguredConsumerApiService : IConsumerApiService
{
private readonly IEnumerable<ConsumerApi> _consumerAPIs;
public ConfiguredConsumerApiService(IOptions<IEnumerable<ConsumerApi>> options)
{
_consumerAPIs = options.Value;
}
public Task<ConsumerApi?> ReadByNameAsync(string name) => Task.Run(() => _consumerAPIs.FirstOrDefault(api => api.Name == name));
public async Task<bool> VerifyAsync(string name, string password) => (await ReadByNameAsync(name))?.Password == password;
}
}

View File

@@ -0,0 +1,19 @@
using DigitalData.Auth.API.Entities;
using DigitalData.Auth.API.Services.Contracts;
using Microsoft.Extensions.Options;
namespace DigitalData.Auth.API.Services
{
public class ConfiguredConsumerService : IConsumerService
{
private readonly IEnumerable<Consumer> _consumerAPIs;
public ConfiguredConsumerService(IOptions<IEnumerable<Consumer>> options)
{
_consumerAPIs = options.Value;
}
public Task<Consumer?> ReadByIdAsync(string id) => Task.Run(() => _consumerAPIs.FirstOrDefault(api => api.Id == id));
public async Task<bool> VerifyAsync(string id, string password) => (await ReadByIdAsync(id))?.Password == password;
}
}

View File

@@ -1,11 +0,0 @@
using DigitalData.Auth.API.Entities;
namespace DigitalData.Auth.API.Services.Contracts
{
public interface IConsumerApiService
{
public Task<ConsumerApi?> ReadByNameAsync(string name);
public Task<bool> VerifyAsync(string name, string password);
}
}

View File

@@ -0,0 +1,11 @@
using DigitalData.Auth.API.Entities;
namespace DigitalData.Auth.API.Services.Contracts
{
public interface IConsumerService
{
public Task<Consumer?> ReadByIdAsync(string id);
public Task<bool> VerifyAsync(string id, string password);
}
}

View File

@@ -6,11 +6,11 @@ namespace DigitalData.Auth.API.Services
{
public static class DIExtensions
{
public static IServiceCollection AddConsumerApiServiceFromConfiguration(this IServiceCollection services, IConfiguration configuration, string key = "ConsumerAPIs")
public static IServiceCollection AddConsumerApiService(this IServiceCollection services, IConfiguration configuration, string key = "ConsumerAPIs")
{
var consumerApis = configuration.GetSection(key).Get<IEnumerable<ConsumerApi>>() ?? throw new InvalidOperationException($"No Consumer list found in {key} in configuration.");
services.AddSingleton(Options.Create(consumerApis));
services.AddSingleton<IConsumerApiService, ConfiguredConsumerApiService>();
var consumers = configuration.GetSection(key).Get<IEnumerable<Consumer>>() ?? throw new InvalidOperationException($"No Consumer list found in {key} in configuration.");
services.AddSingleton(Options.Create(consumers));
services.AddSingleton<IConsumerService, ConfiguredConsumerService>();
return services;
}
}

View File

@@ -0,0 +1,19 @@
{
"ConsumerAPIs": [
{
"Id": "auth",
"Audience": "auth.digitaldata.works",
"Password": "aQ9z!2@TgY7b#fHcD3pLmV1$wX"
},
{
"Id": "work-flow",
"Audience": "work-flow.digitaldata.works",
"Password": "t3B|aiJ'i-snLzNRj3B{9=&:lM5P@'iL"
},
{
"Id": "user-manager",
"Audience": "user-manager.digitaldata.works",
"Password": "a098Hvu1-y29ep{KPQO]#>8TK+fk{O`_d"
}
]
}

View File

@@ -1,12 +0,0 @@
{
"ConsumerAPIs": [
{
"Name": "WorkFlow.API",
"Password": "t3B|aiJ'i-snLzNRj3B{9=&:lM5P@'iL"
},
{
"Name": "DigitalData.UserManager.API",
"Password": "a098Hvu1-y29ep{KPQO]#>8TK+fk{O`_d"
}
]
}