I'm using the following function to check whether the person that wants to access some database record is an owner of this record:
public class AccessGuard
{
public async Task<bool> IsOwnerOrHaveRightsAsync(UserManager<ApplicationUser> userManager, ApplicationUser claimant, ClaimsPrincipal User)
{
ApplicationUser fullUser = await userManager.GetUserAsync(User);
if (claimant.Id == fullUser.Id)
{
return true;
}
return false;
}
}
It works, but as I've noticed: ApplicationUser
is now added to ChangeTracker. What it means is I cannot call userManager.GetUserAsync
later in code, because I get this error:
The instance of entity type 'ApplicationUser' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I usually use .AsNoTracking()
while accessing database records, but there is nothing like that in userManager. How would you solve this?
I am using it in MVC Controller method as follows:
if (!await new AccessGuard().IsOwnerOrHaveRightsAsync(_userManager, Post.Author, User))
{
return Unauthorized();
}
You cannot use .AsNoTracking()
with await userManager.GetUserAsync(User);
. Alternatively you can do as follows:
public class AccessGuard
{
private readonly ApplicationDbContext _context;
private readonly IHttpContextAccessor _httpContextAccessor;
public AccessGuard(ApplicationDbContext context, IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
_context = context;
}
public async Task<bool> IsOwnerOrHaveRightsAsync(UserManager<ApplicationUser> userManager, ApplicationUser claimant, ClaimsPrincipal User)
{
var loggedInUserId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
ApplicationUser fullUser = _context.ApplicationUsers.AsNoTracking()
.FirstOrDefaultAsync(au => au.Id == loggedInUserId);
if (claimant.Id == fullUser.Id)
{
return true;
}
return false;
}
}
Then you should register IHttpContextAccessor
in the Startup
class as follows:
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Or you can also register as follows
services.AddHttpContextAccessor();
}
Then to access AccessGuard
service in your MVC controller method, first register AccessGuard
in Startup as follows:
services.AddScoped<AccessGuard>();
Then in your controller method:
public IActionResult Index()
{
AccessGuard accessGuardService = (AccessGuard) HttpContext.RequestServices.GetService(typeof(AccessGuard));
// Now call `accessGuardService` service method here
return View();
}
You can also get AccessGuard
service as follows:
AccessGuard accessGuardService = HttpContext.RequestServices.GetService<AccessGuard>();
and it requires namespace using Microsoft.Extensions.DependencyInjection;