Jasinski Technical Wiki


Home Page
All Pages

Quick Search
Advanced Search »

Contributor Links

Create a new Page
File Management
Your Profile

Other Wiki Sections



Data-Level Security - ASP.NET Core and EF Core

Modified on Fri, Aug 27, 2021, 10:26 AM by Administrator Categorized as ASP·NET MVC, ASP·NET Security, EF Core



Reusable Code


  • _context and _db is a DatabaseContext
  • AuthMeta and AuthorizedMeta.Current refer to an object which contains info about the currently logged-in user.
  • UserTypeId is a general categorization of users, which may or may not be used in a particular system.

IUserCanAccess Interface

public interface IUserCanAccess
    bool UserCanAccessEntity(NetSoftDataModel db, int id, string userId, int? labId, UserTypeEnum userTypeId);

SecurityHelper Static Class

using Microsoft.AspNetCore.Http;
using System;

public static class SecurityHelper
    public  static int? GetLabId(HttpRequest request, bool allowNull = false)
        /*--- If the user is assigned a lab, return that lab ---*/
        if (AuthorizedMeta.Current?.LabId != null)
            return AuthorizedMeta.Current.LabId;

        /*--- Otherwise, check the query string ---*/
        var labId = GetQueryStringItem(request, "labId");

        if (labId == null && !allowNull)
            throw new Exception("Invalid Lab ID");

        return labId;
    private static int? GetQueryStringItem(HttpRequest request, string key)
        var val = request.Query.ContainsKey(key) ?
            request.Query[key][0] :

        if (int.TryParse(val, out int result))
            return result;

        return null;


using System;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ValidateUserCanAccessAttribute : Attribute
    public string IdRouteParameterName { get; private set; }
    public Type EntityType { get; private set; }
    public ValidateUserCanAccessAttribute(Type entityType, string idRouteParameterName)
        EntityType = entityType;
        IdRouteParameterName = idRouteParameterName;

ValidateUserCanAccess Controller Extension Method

protected void ValidateUserCanAccess<T>(int id)
    where T : IUserCanAccess, new()
    var a = new T();

    /* TODO: Replace "Tenant" with the name of your tenant table.    */
    /* If not a multi-tenant application, replace with string.Empty. */
    var labId = a.GetType().Name == "Tenant" ? 
        AuthorizedMeta.Current.LabId : 

    if (!a.UserCanAccessEntity(_context, id, AuthMeta.UserId, labId, AuthorizedMeta.Current.UserTypeId))
        throw new UnauthorizedAccessException($"User can't access {a.GetType().Name} {id}.");

GetRepoItems Method

This method can be added to the DataModel class to enforce data-level security on an entity throughout the application. NOTE: This assumes the DbSet for the entity cannot be accessed from outside the data layer - that is, it's declared "internal" or "private".

public IQueryable<T> GetRepoItems<T>()
    where T: class, new()
    var modelType = this.GetType();

    var totFullName = (typeof(T)).FullName;

    var prop = modelType.GetProperties()
        .FirstOrDefault(p =>
            && p.PropertyType.Name == "DbSet`1"
            && p.PropertyType.GenericTypeArguments[0].FullName == totFullName

    var dbSet = prop.GetValue(this) as DbSet<T>;

    var q = dbSet?.AsQueryable<T>();

    if (q != null && typeof(IWhereUserCanAccess<T>).IsAssignableFrom(typeof(T)))
        var userId = AuthorizedMeta.Current.UserId;

        // Note: AuthorizedMeta.Current.SelectedTenantId gets set in the global authorization
        // filter code based on a query string parameter.
        var tenantId = AuthorizedMeta.Current.SelectedTenantId;

        var userTypeId = AuthorizedMeta.Current.UserTypeId;

        q = q.Where((new T() as IWhereUserCanAccess<T>).WhereUserCanAccess(userId, tenantId, userTypeId));

    return q;

IWhereUserCanAccess Interface

public interface IWhereUserCanAccess<T>
    Expression<Func<T, bool>> WhereUserCanAccess(string userId, int? selectedTenantId,
        UserTypeEnum userTypeId);


(1) Each data entity we want to enforce data-level security on should implement the IWhereUserCanAccess interface. Here's an example.

public class Item 
    public Expression<Func<Item, bool>> WhereUserCanAccess(string userId, int? selectedTenantId,
        UserTypeEnum userTypeId)
        return a => !a.IsDeleted && a.TenantId == selectedTenantId;

(2) The DbSet needs to be declared as "internal" instead of "public". This prevents code from outside the data later from bypassing the data-level security.

(3) Example service code

using (var db = new MyDataModel())
    var dbItem = db.GetRepoItems<Item>().FirstOrDefault(a => a.Id == id);

ScrewTurn Wiki version Some of the icons created by FamFamFam. Except where noted, all contents Copyright © 1999-2021, Patrick Jasinski.