Files
missions/Auth/JwtExtensions.cs
T
Oleksandr Bezdieniezhnykh 7025f4d075 refactor: enhance JWT authentication and CORS configuration
Updated JWT authentication to use configuration values instead of hardcoded secrets, improving security and flexibility. Enhanced CORS policy to conditionally allow origins based on configuration settings, with logging for permissive defaults. Updated README to reflect project renaming and clarify service context.
2026-05-14 19:48:25 +03:00

89 lines
4.2 KiB
C#

using Azaion.Flights.Infrastructure;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens;
namespace Azaion.Flights.Auth;
public static class JwtExtensions
{
public const string JwtIssuerEnvVar = "JWT_ISSUER";
public const string JwtIssuerConfigKey = "Jwt:Issuer";
public const string JwtAudienceEnvVar = "JWT_AUDIENCE";
public const string JwtAudienceConfigKey = "Jwt:Audience";
public const string JwtJwksUrlEnvVar = "JWT_JWKS_URL";
public const string JwtJwksUrlConfigKey = "Jwt:JwksUrl";
public static IServiceCollection AddJwtAuth(this IServiceCollection services, IConfiguration configuration)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
var issuer = ConfigurationResolver.ResolveRequiredOrThrow(configuration, JwtIssuerEnvVar, JwtIssuerConfigKey, "JWT issuer");
var audience = ConfigurationResolver.ResolveRequiredOrThrow(configuration, JwtAudienceEnvVar, JwtAudienceConfigKey, "JWT audience");
var jwksUrl = ConfigurationResolver.ResolveRequiredOrThrow(configuration, JwtJwksUrlEnvVar, JwtJwksUrlConfigKey, "JWKS URL");
// JwtBearer's stock ConfigurationManager targets the full OIDC discovery
// document; admin only exposes JWKS, so we wire a JWKS-only retriever.
// The manager caches the document and refreshes on the default schedule
// (matches admin's Cache-Control: public, max-age=3600 on /.well-known/jwks.json).
var jwksConfigManager = new ConfigurationManager<JsonWebKeySet>(
jwksUrl,
new JwksRetriever(),
new HttpDocumentRetriever { RequireHttps = true });
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
ValidAudience = audience,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
// Pin algorithms so a token forged with alg=HS256 using the
// public key as the HMAC secret cannot pass validation.
ValidAlgorithms = [SecurityAlgorithms.EcdsaSha256],
RequireSignedTokens = true,
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromSeconds(30),
IssuerSigningKeyResolver = (_, _, kid, _) =>
{
var jwks = jwksConfigManager
.GetConfigurationAsync(CancellationToken.None)
.GetAwaiter()
.GetResult();
if (string.IsNullOrEmpty(kid))
return jwks.GetSigningKeys();
return jwks.GetSigningKeys().Where(k => k.KeyId == kid);
}
};
});
services.AddAuthorizationBuilder()
.AddPolicy("FL", p => p.RequireClaim("permissions", "FL"))
.AddPolicy("GPS", p => p.RequireClaim("permissions", "GPS"));
return services;
}
// ConfigurationManager<JsonWebKeySet> needs an IConfigurationRetriever<JsonWebKeySet>.
// Microsoft ships OpenIdConnectConfigurationRetriever (full discovery doc) but
// no JWKS-only equivalent, so we implement the minimal version here.
private sealed class JwksRetriever : IConfigurationRetriever<JsonWebKeySet>
{
public async Task<JsonWebKeySet> GetConfigurationAsync(string address, IDocumentRetriever retriever, CancellationToken cancel)
{
ArgumentNullException.ThrowIfNull(address);
ArgumentNullException.ThrowIfNull(retriever);
var document = await retriever.GetDocumentAsync(address, cancel).ConfigureAwait(false);
return new JsonWebKeySet(document);
}
}
}