I am currently working on an ASP.NET Core application and I want my application to have multi-tenant capabilities, so I added a TenancyProvider service that gets injected into my DbContext (so that I can setup a global query filter later):
public class TenancyProvider : ITenancyProvider
{
private readonly IHttpContextAccessor _context;
public TenancyProvider(IHttpContextAccessor context)
{
_context = context;
}
public string? AuthenticatedUser() => _context.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
My problem now is that this method always returns null. When inspecting the call in debug mode, I saw that the ClaimsPrincipal does not contain any data, even after login. There are always 0 claims and IsAuthenticated is always false, so I guess there is a problem with the cookie or reading the data from it. In the Chrome Dev Tools I can see at least that an Identity Cookie exists.
For authentication I use AspNetCore.Identity. Here's the current code of my login service:
[HttpPost]
public async Task<IActionResult> Login(LoginDTO loginDTO, string? ReturnUrl)
{
if (ModelState.IsValid == false)
{
ViewBag.Errors = ModelState.Values
.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage);
return View(loginDTO);
}
var user = await _userManager.FindByEmailAsync(loginDTO.Email);
if (user == null)
{
ModelState.AddModelError("Login", "Ungültiger Benutzer");
return View(loginDTO);
}
var result = await _signInManager.PasswordSignInAsync(loginDTO.Email, loginDTO.Password, isPersistent: false, lockoutOnFailure: false);
if (result.Succeeded)
{
//Return URL Redirect
if (!string.IsNullOrEmpty(ReturnUrl) && Url.IsLocalUrl(ReturnUrl))
{
return LocalRedirect(ReturnUrl);
}
return RedirectToAction("Index", "Orders");
}
ModelState.AddModelError("Login", "Ungültiger Benutzername oder Passwort");
return View(loginDTO);
}
I also tried to add claims manually, but that did not help the situation, so I removed it again.
Here's my Program.cs:
...
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<ITenancyProvider, TenancyProvider>();
builder.Services.AddDbContext<ApplicationDbContext>(
options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
builder.Services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddUserStore<UserStore<ApplicationUser, ApplicationRole, ApplicationDbContext, Guid>>()
.AddRoleStore<RoleStore<ApplicationRole, ApplicationDbContext, Guid>>();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
});
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
else
{
app.UseDeveloperExceptionPage();
}
app.UseHsts();
app.UseHttpsRedirection();
Rotativa.AspNetCore.RotativaConfiguration.Setup("wwwroot", wkhtmltopdfRelativePath: "Rotativa");
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
//app.MapRazorPages();
app.Run();
I wouldn't be surprised if it is just a small thing that I am missing here, but I have already spent hours now searching the web and trying out different solutions.
Let me know if you need additional info. I am really looking forward to your answers.
Cheers
Update Here's the code where I call the Tenancy Provider in my DB Context:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
private readonly string _tenantId;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ITenancyProvider tenancyProvider) : base(options)
{
_tenantId = tenancyProvider.AuthenticatedUser();
}
...
And here is a screenshot that shows the empty Claims list and that IsAuthenticated is false. The user was already logged in by the time I took the screenshot.

The application behaves like the user is logged in though. I can access Controllers and Pages I cannot access if I am not logged in.
Apparently, the problem was that you cannot access the user from the
HttpContextfrom theDbContextconstructor. I found a solution that works for my use case here Stackoverflow discussion. More details can also be found in this Github issue.Here's what you can do:
You need to delay the fetching of the user's information until you actually need them. Therefore, I now just inject the
TenancyProviderservice into my constructor. I have also introduced a property that holds the tenant id and the getter calls theTenancyProvider. And that's basically it.My
DbContext:I simply do in my
OnModelCreating()