- 
                Notifications
    
You must be signed in to change notification settings  - Fork 10
 
Description
I integrated the DarkLoop.Azure.Functions.Authorization.Isolated Nuget package into my .NET 9 isolated Function App project, and it works great overall. But I had a CurrentUserService I was using before that worked fine, but once I implemented DarkLoop, it seems to have stopped working. In the constructor, IHttpContextAccessor httpContextAccessor is always null, no matter what. Here is that class:
namespace MyFunctionApp.Core.Services
{
    public class CurrentUserService : ICurrentUserService
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        public CurrentUserService(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }
        public string? UserId
        {
            get
            {
                return _httpContextAccessor.HttpContext?.User?.FindFirstValue(JwtRegisteredClaimNames.Sub);
            }
        }
        public string? Username
        {
            get
            {
                return _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.Name);
            }
        }
    }
}I've tried moving the statements in my Program.cs around, but nothing changes the fact that the httpContextAccessor is always null. Is there any way to continue using the CurrentUserService, or am I going to have to completely refactor it or use something else entirely?
Here is my Program.cs:
using DarkLoop.Azure.Functions.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.Azure.Functions.Worker;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = Host
    .CreateDefaultBuilder(args)
    .ConfigureFunctionsWebApplication(builder =>
    {
        builder.UseFunctionsAuthorization();
        builder.Services.AddHttpContextAccessor();
    })
    .ConfigureServices((ctx, services) =>
    {
        var config = ctx.Configuration;
        // Key Vault
        services.AddSingleton<IKeyVaultService, KeyVaultService>();
        // Application services & HTTP client
        services.AddApplicationServices();
        // Identity
        services.AddIdentityCore<ApplicationUser>(opts =>
        {
            opts.Password.RequireDigit = true;
            opts.Password.RequireLowercase = true;
            opts.Password.RequireUppercase = true;
            opts.Password.RequireNonAlphanumeric = false;
            opts.Password.RequiredLength = 8;
        })
            .AddRoles<IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();
        // JWT Authentication & Authorization Policies
        services
            .AddFunctionsAuthentication(JwtFunctionsBearerDefaults.AuthenticationScheme)
            .AddJwtFunctionsBearer(options =>
            {
                options.SaveToken = true;
                options.RequireHttpsMetadata = true;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.Zero
                };
            });
        services.AddFunctionsAuthorization(opts =>
        {
            opts.ConfigurePolicies();
        });
        // Current user & EF interceptor
        services.AddScoped<ICurrentUserService, CurrentUserService>();
        services.AddScoped<AuditableEntityInterceptor>();
        // Build the service provider.
        var sp = services.BuildServiceProvider();
        var keyVaultService = sp.GetRequiredService<IKeyVaultService>();
        // Resolve JWT settings from KeyVault
        var jwtSettingsJson = keyVaultService
            .GetSecretAsync(Constants.KEYVAULT_KEY_JWT_TOKEN_SETTINGS_JSON)
            .GetAwaiter().GetResult();
        var jwtSettings = System.Text.Json.JsonSerializer.Deserialize<JwtTokenSettings>(jwtSettingsJson ?? "", JsonHelper.GetDefaultJsonSerializerOptions())!;
        // Resolve JWT settings from KeyVault.
        services.PostConfigure<JwtBearerOptions>(
            JwtFunctionsBearerDefaults.AuthenticationScheme,
            options =>
            {
                options.TokenValidationParameters.IssuerSigningKey =
                    new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey));
                options.TokenValidationParameters.ValidAudience = jwtSettings.Audience;
                options.TokenValidationParameters.ValidIssuer = jwtSettings.Issuer;
                options.TokenValidationParameters.ValidateIssuerSigningKey = true;
                options.TokenValidationParameters.ValidateIssuer = true;
                options.TokenValidationParameters.ValidateAudience = true;
                options.TokenValidationParameters.ValidateLifetime = true;
                options.TokenValidationParameters.ClockSkew = TimeSpan.Zero;
            });
    });
var host = builder.Build();
host.Run();Lastly, I have the AuditableEntityInterceptor class that needs CurrentUserService to get the current user's ID, but it obviously doesn't work right now either:
namespace MyProject.Infrastructure.Data.Interceptors
{
    public class AuditableEntityInterceptor : SaveChangesInterceptor
    {
        private readonly ICurrentUserService _currentUserService;
        public AuditableEntityInterceptor(ICurrentUserService currentUserService)
        {
            _currentUserService = currentUserService;
        }
        public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
        {
            UpdateEntities(eventData.Context);
            return base.SavingChanges(eventData, result);
        }
        public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
        {
            UpdateEntities(eventData.Context);
            return base.SavingChangesAsync(eventData, result, cancellationToken);
        }
        private void UpdateEntities(DbContext? context)
        {
            if (context == null)
            {
                return;
            }
            var userId = _currentUserService.UserId;
            var now = DateTime.UtcNow;
            foreach (var entry in context.ChangeTracker.Entries().Where(e => e.Entity is BaseEntity<object>))
            {
                var entity = entry.Entity as BaseEntity<object>;
                if (entity == null)
                {
                    continue;
                }
                if (entry.State == EntityState.Added)
                {
                    entity.CreatedBy = userId ?? string.Empty;
                    entity.CreatedDate = now;
                }
                else if (entry.State == EntityState.Modified)
                {
                    entity.LastModifiedBy = userId;
                    entity.LastModifiedDate = now;
                }
                else if (entry.State == EntityState.Deleted)
                {
                    entry.State = EntityState.Modified;
                    entity.IsDeleted = true;
                    entity.DeletedBy = userId;
                    entity.DeletedDate = now;
                }
            }
        }
    }
}