- Fix GraphQL authorization attributes to use string[] instead of string for roles - Remove admin role requirement from ImportNovel endpoint - Add comprehensive OIDC configuration validation with specific error messages - Validate Authority, ClientId, and Audience are properly configured - Ensure HTTPS requirement except for localhost development Co-authored-by: conco <conco@users.noreply.local>
121 lines
4.9 KiB
C#
121 lines
4.9 KiB
C#
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using FictionArchive.Service.Shared.Models.Authentication;
|
|
using System.Linq;
|
|
|
|
namespace FictionArchive.Service.Shared.Extensions;
|
|
|
|
public static class AuthenticationExtensions
|
|
{
|
|
public static IServiceCollection AddOidcAuthentication(this IServiceCollection services, IConfiguration configuration)
|
|
{
|
|
var oidcConfig = configuration.GetSection("OIDC").Get<OidcConfiguration>();
|
|
|
|
if (oidcConfig == null)
|
|
{
|
|
throw new InvalidOperationException("OIDC configuration is required but not found in app settings");
|
|
}
|
|
|
|
ValidateOidcConfiguration(oidcConfig);
|
|
|
|
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
.AddJwtBearer(options =>
|
|
{
|
|
options.Authority = oidcConfig.Authority;
|
|
options.Audience = oidcConfig.Audience;
|
|
options.RequireHttpsMetadata = !string.IsNullOrEmpty(oidcConfig.Authority) && oidcConfig.Authority.StartsWith("https://");
|
|
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuer = oidcConfig.ValidateIssuer,
|
|
ValidateAudience = oidcConfig.ValidateAudience,
|
|
ValidateLifetime = oidcConfig.ValidateLifetime,
|
|
ValidateIssuerSigningKey = oidcConfig.ValidateIssuerSigningKey,
|
|
ClockSkew = TimeSpan.FromMinutes(5)
|
|
};
|
|
});
|
|
|
|
return services;
|
|
}
|
|
|
|
public static IServiceCollection AddOidcCookieAuthentication(this IServiceCollection services, IConfiguration configuration, string cookieName = "fa_session")
|
|
{
|
|
var oidcConfig = configuration.GetSection("OIDC").Get<OidcConfiguration>();
|
|
|
|
if (oidcConfig == null)
|
|
{
|
|
throw new InvalidOperationException("OIDC configuration is required but not found in app settings");
|
|
}
|
|
|
|
ValidateOidcConfiguration(oidcConfig);
|
|
|
|
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
.AddJwtBearer(options =>
|
|
{
|
|
options.Authority = oidcConfig.Authority;
|
|
options.Audience = oidcConfig.Audience;
|
|
options.RequireHttpsMetadata = !string.IsNullOrEmpty(oidcConfig.Authority) && oidcConfig.Authority.StartsWith("https://");
|
|
|
|
options.Events = new JwtBearerEvents
|
|
{
|
|
OnMessageReceived = context =>
|
|
{
|
|
// Try to get token from cookie first, then from Authorization header
|
|
if (context.Request.Cookies.ContainsKey(cookieName))
|
|
{
|
|
context.Token = context.Request.Cookies[cookieName];
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
};
|
|
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuer = oidcConfig.ValidateIssuer,
|
|
ValidateAudience = oidcConfig.ValidateAudience,
|
|
ValidateLifetime = oidcConfig.ValidateLifetime,
|
|
ValidateIssuerSigningKey = oidcConfig.ValidateIssuerSigningKey,
|
|
ClockSkew = TimeSpan.FromMinutes(5)
|
|
};
|
|
});
|
|
|
|
return services;
|
|
}
|
|
|
|
public static IServiceCollection AddFictionArchiveAuthorization(this IServiceCollection services)
|
|
{
|
|
services.AddAuthorizationBuilder()
|
|
.AddPolicy("Admin", policy => policy.RequireRole("admin"))
|
|
.AddPolicy("User", policy => policy.RequireAuthenticatedUser());
|
|
|
|
return services;
|
|
}
|
|
|
|
private static void ValidateOidcConfiguration(OidcConfiguration config)
|
|
{
|
|
var errors = new List<string>();
|
|
|
|
if (string.IsNullOrWhiteSpace(config.Authority))
|
|
errors.Add("OIDC Authority is required");
|
|
|
|
if (string.IsNullOrWhiteSpace(config.ClientId))
|
|
errors.Add("OIDC ClientId is required");
|
|
|
|
if (string.IsNullOrWhiteSpace(config.Audience))
|
|
errors.Add("OIDC Audience is required");
|
|
|
|
if (!Uri.TryCreate(config.Authority, UriKind.Absolute, out var authorityUri))
|
|
errors.Add($"OIDC Authority '{config.Authority}' is not a valid URI");
|
|
else if (!authorityUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) &&
|
|
!authorityUri.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase))
|
|
errors.Add("OIDC Authority must use HTTPS unless running on localhost");
|
|
|
|
if (errors.Any())
|
|
{
|
|
throw new InvalidOperationException($"OIDC configuration validation failed:{Environment.NewLine}{string.Join(Environment.NewLine, errors)}");
|
|
}
|
|
}
|
|
} |