Enhance AppConfig with JWT settings and update Program.cs for improved authentication handling. Modify README.md to reflect new environment variable structure for configuration.

This commit is contained in:
2026-01-20 15:51:35 +05:00
parent 4655b4111f
commit 71a0fb48fe
4 changed files with 87 additions and 50 deletions

View File

@@ -12,7 +12,16 @@ public class AppConfig
public string? InsecureDevEmail { get; set; }
public string[] AllowedOrigins { get; set; } = ["http://localhost:3000", "https://localhost:3000"];
// JWT configuration
public string NameClaimType { get; set; } = "name";
public string RoleClaimType { get; set; } = "role";
public string EmailClaimType { get; set; } = "email";
public string AuthRequestEmailHeader { get; set; } = "X-Auth-Request-Email";
public TimeSpan ClockSkew { get; set; } = TimeSpan.FromMinutes(5);
public string ConnectionString =>
$"Host={DbHost};Port={DbPort};Database={DbName};Username={DbUser};Password={DbPassword}";
}

View File

@@ -0,0 +1,36 @@
using DexDemoBackend;
using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
using Microsoft.Extensions.Options;
using System.Text.Encodings.Web;
public class InsecureDevAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly AppConfig _config;
public InsecureDevAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
AppConfig config) : base(options, logger, encoder)
{
_config = config;
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var email = _config.InsecureDevEmail ?? "dev@example.com";
var claims = new[]
{
new Claim(ClaimTypes.Name, email),
new Claim(_config.EmailClaimType, email)
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}

View File

@@ -1,5 +1,6 @@
using Dapper;
using DexDemoBackend;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -8,10 +9,10 @@ using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens;
using Npgsql;
using System.Security.Claims;
using Microsoft.Extensions.Logging;
var builder = WebApplication.CreateBuilder(args);
// Configure AppConfig using IConfiguration
builder.Services.Configure<AppConfig>(builder.Configuration.GetSection("AppConfig"));
builder.Services.AddSingleton(sp => sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<AppConfig>>().Value);
@@ -31,7 +32,6 @@ builder.Services.ConfigureHttpJsonOptions(options =>
var app = builder.Build();
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
// Get current config from DI (supports reload)
var currentConfig = app.Services.GetRequiredService<AppConfig>();
app.UseCors(policy =>
@@ -44,7 +44,6 @@ app.UseCors(policy =>
app.UseAuthentication();
app.UseAuthorization();
// Global error handling middleware
app.Use(async (context, next) =>
{
try
@@ -140,6 +139,14 @@ app.Run();
static void ConfigureJwtAuthentication(IServiceCollection services, AppConfig config)
{
if (config.InsecureDevMode)
{
services.AddAuthentication("InsecureDev")
.AddScheme<AuthenticationSchemeOptions, InsecureDevAuthenticationHandler>("InsecureDev", _ => { });
return;
}
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
@@ -152,35 +159,33 @@ static void ConfigureJwtAuthentication(IServiceCollection services, AppConfig co
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = OidcConfigConstants.ClockSkew,
NameClaimType = OidcConfigConstants.NameClaimType,
RoleClaimType = OidcConfigConstants.RoleClaimType
ClockSkew = config.ClockSkew,
NameClaimType = config.NameClaimType,
RoleClaimType = config.RoleClaimType
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var nameClaim = context.Principal?.FindFirst(OidcConfigConstants.NameClaimType);
var emailClaim = context.Principal?.FindFirst(OidcConfigConstants.EmailClaimType);
var nameClaim = context.Principal?.FindFirst(config.NameClaimType);
var emailClaim = context.Principal?.FindFirst(config.EmailClaimType);
if (nameClaim == null && emailClaim != null)
{
var identity = context.Principal?.Identity as ClaimsIdentity;
identity?.AddClaim(new Claim(OidcConfigConstants.NameClaimType, emailClaim.Value));
identity?.AddClaim(new Claim(config.NameClaimType, emailClaim.Value));
}
return Task.CompletedTask;
}
};
var httpClientHandler = new HttpClientHandler();
if (config.InsecureDevMode)
var httpClientHandler = new HttpClientHandler
{
httpClientHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
}
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
var discoveryUrl = $"{config.Issuer}.well-known/openid-configuration";
options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
@@ -224,21 +229,16 @@ static void ValidateConfiguration(AppConfig config)
static Task<string> GetUserEmail(HttpContext context, AppConfig config)
{
if (config.InsecureDevMode)
{
return Task.FromResult(config.InsecureDevEmail!);
}
if (context.User.Identity?.IsAuthenticated == true)
{
var emailClaim = context.User.FindFirst(OidcConfigConstants.EmailClaimType);
var emailClaim = context.User.FindFirst(config.EmailClaimType);
if (emailClaim != null)
{
return Task.FromResult(emailClaim.Value);
}
}
if (context.Request.Headers.TryGetValue(OidcConfigConstants.AuthRequestEmailHeader, out var emailHeader))
if (context.Request.Headers.TryGetValue(config.AuthRequestEmailHeader, out var emailHeader))
{
return Task.FromResult(emailHeader.ToString());
}
@@ -271,23 +271,3 @@ record UserIdentityResponse(
string? AuthenticationType,
List<ClaimResponse> Claims
);
static class OidcConfigConstants
{
// Default environment variable values
public const string DefaultDbHost = "postgres";
public const string DefaultDbPort = "5440";
public const string DefaultDbName = "dexdemo";
public const string DefaultDbUser = "dexdemo";
public const string DefaultDbPassword = "dexdemo";
public const string DefaultDexIssuer = "https://dex.127.0.0.1.sslip.io/";
// JWT settings
public const string NameClaimType = "name";
public const string RoleClaimType = "role";
public const string EmailClaimType = "email";
public const string AuthRequestEmailHeader = "X-Auth-Request-Email";
// Time settings
public static readonly TimeSpan ClockSkew = TimeSpan.FromMinutes(5);
}

View File

@@ -13,9 +13,9 @@ dotnet run
```bash
docker build -t dex-demo-backend-dotnet:latest .
docker run -p 8000:8000 \
-e DB_HOST=postgres \
-e DB_PORT=5440 \
-e DEX_ISSUER=https://dex.127.0.0.1.sslip.io/ \
-e AppConfig__DbHost=postgres \
-e AppConfig__DbPort=5440 \
-e AppConfig__Issuer=https://dex.127.0.0.1.sslip.io/ \
dex-demo-backend-dotnet:latest
```
@@ -34,8 +34,20 @@ docker run -p 8000:8000 \
## Переменные окружения
Все переменные идентичны Python версии:
- `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
- `DEX_ISSUER`
- `INSECURE_DEV_MODE`, `INSECURE_DEV_EMAIL`
### База данных
- `AppConfig__DbHost` - хост PostgreSQL (по умолчанию: postgres)
- `AppConfig__DbPort` - порт PostgreSQL (по умолчанию: 5440)
- `AppConfig__DbName` - имя базы данных (по умолчанию: dexdemo)
- `AppConfig__DbUser` - пользователь базы данных (по умолчанию: dexdemo)
- `AppConfig__DbPassword` - пароль базы данных (по умолчанию: dexdemo)
### Аутентификация
- `AppConfig__Issuer` - URL Dex сервера (по умолчанию: https://dex.127.0.0.1.sslip.io/)
### Режим разработки
- `AppConfig__InsecureDevMode` - включить небезопасный режим разработки (true/false)
- `AppConfig__InsecureDevEmail` - email для тестирования в режиме разработки
### CORS
- `AppConfig__AllowedOrigins` - разрешенные origins для CORS (JSON массив)