I have a weird situation in my asp.net core api project. I am tryin to access the ActionContext via the IActionContextAccessor is an AuthenticationHandler. Now I found out, that when I have only 1 AuthenticationScheme registered, the returned ActionContext is null, but once I add a second AuthenticationScheme, the ActionContext is properly returned.
The code below is a minimized reproduction scenario. I have a program.cs that sets up the authentication schemes. When I call a random api route in the project, and have a breakpoint in the HandleAuthenticateAsync method, I see the ActionContext is filled. However, once I comment out the line that registers the Schema2 authenticationhandler, the ActionContext becomes null. When hitting the same breakpoint.
Can anyone explain what is happening here?
my program.cs:
using AuthHandlerTest;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Infrastructure;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
builder.Services.AddControllers(options =>
{
options.Filters.Add(new AuthorizeFilter());
});
builder.Services.AddAuthentication()
.AddScheme<Schema1Options, Schema1>("schema1", null);
.AddScheme<Schema2Options, Schema2>("schema2", null);
builder.Services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder("schema1").RequireAuthenticatedUser().Build();
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthentication();
app.UseRouting();
app.MapControllers();
app.Run();
my Schema1.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Text.Encodings.Web;
namespace AuthHandlerTest;
public class Schema1Options : AuthenticationSchemeOptions
{
}
public class Schema1 : AuthenticationHandler<Schema1Options>
{
private readonly IActionContextAccessor _actionContextAccessor;
public Schema1(IOptionsMonitor<Schema1Options> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IActionContextAccessor actionContextAccessor) : base(options, logger, encoder, clock)
{
_actionContextAccessor = actionContextAccessor;
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
//this variable is null when only having 1 scheme registered
var actionContext = _actionContextAccessor.ActionContext;
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, "aa"),
};
var claimsIdentity = new ClaimsIdentity(claims, "schema1");
var ticket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), "schema1");
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
schema2.cs is identical to schema1.cs, only the 1's are replaced with 2's.
I also see that the stacktrace is significantly different between the working and non-working scenario.
Now I understand why these different stacks are causing the ActionContext to be available or not, because in the working situation the ResourceInvoker is present in the callstack, and this component makes sure the IActionContextAccessor gets the ActionContext property filled. The million dollar question is, what is causing the difference in callstack
Update: I've added a full reproduction scenario here



