0

is it possible to get data from an attribute in middleware before the page is loaded? Meaning if I attach an attribute to a controller, can I access the data in middleware?

My for now empty attribute:

public sealed class Secure : Attribute
{
    public Secure()
    {

    }

    public Secure(params string[] roles)
    {

    }
}
SteinTheRuler
  • 3,549
  • 4
  • 35
  • 71
  • 1
    Is this solely for authorization? Take a look at `AuthorizeAttribute`, `IAuthorizationFilter`, `IAsyncAuthorizationFilter`. – muratgu Jan 11 '21 at 00:07

3 Answers3

2

Found a nice example of how to access attributes in middleware.
Source: https://michaelscodingspot.com/attributes-and-middleware-in-asp-net-core/

Create attribute:

public class TelemetryAttribute : Attribute
{
    public TelemetryEvent Event { get; set; }
        
    public TelemetryAttribute(TelemetryEvent ev)
    {
        Event = ev;
    }
}
 
public enum TelemetryEvent { SignUp, SignIn}

Use the attribute:

[TelemetryAttribute(TelemetryEvent.SignUp)]
public async Task<IActionResult> SignUp([FromBody]SignUpViewModel vm)
{
    // ...
}

Use attribute params in middleware:

public class TelemetryMiddleware
{
    private RequestDelegate _next;
 
    public TelemetryMiddleware(RequestDelegate next)
    {
        _next = next;
    }
 
    public async Task Invoke(HttpContext context)
    {
        await _next(context);
            
        var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
        var attribute = endpoint?.Metadata.GetMetadata<TelemetryAttribute>();
        if (attribute != null && attribute.Event == TelemetryEvent.SignUp)
        {
            // your code here
        }
    }
}
Pinka
  • 201
  • 2
  • 6
  • Thanks. Any reason for the setter on the public attribute property? For anyone looking for passing multiple values into the attribute: `public TelemetryAttribute(params TelemetryEvent[] events){ ... }` (`List` won't work because it's not a compile-time constant and attributes are evaluated during compile time). – user3625699 Apr 27 '23 at 22:02
  • No specific reason for the setter. – Pinka Apr 28 '23 at 12:44
1

I think you'd better custom ActionFilterAttribute to get the data before action excutes:

public class SecureAttribute : ActionFilterAttribute
{
    private readonly UserManager<ApplicationUser> _userManager;
    public SecureAttribute(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        //get data from query string
        if (context.ActionArguments.TryGetValue("returnUrl", out object value))
        {
            //returnUrl is the query string key name
            var query = value.ToString();
        }
        //get data from form
        if (context.ActionArguments.TryGetValue("test", out object model))
        {
            var data = model;  
        }
        //get data from log in User
        var USER = context.HttpContext.User;
        if(USER.Identity.IsAuthenticated)
        {
            var user =  _userManager.FindByNameAsync(USER.Identity.Name).Result;
            var roles = _userManager.GetRolesAsync(user).Result;
        }
        base.OnActionExecuting(context);
    }
}

Controller:

[ServiceFilter(typeof(SecureAttribute))]
public async Task<IActionResult> Index(string returnUrl)
{...}

[HttpPost]
[ServiceFilter(typeof(SecureAttribute))]
public IActionResult Index(Test test)
{
    return View(test);
}

Startup.cs:

services.AddScoped<SecureAttribute>();

If you use Identity to get the role,be sure register service like below:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("YourConnectionString")));
   
    services.AddIdentity<IdentityUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddScoped<SecureAttribute>();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

Result: enter image description here

Rena
  • 30,832
  • 6
  • 37
  • 72
  • is it possible to pass arguments to an ActionFilter? – SteinTheRuler Jan 12 '21 at 17:54
  • Where is the arguments from?In my answer,I could get the arguments from query string or form or logged in User. – Rena Jan 13 '21 at 01:26
  • I must be able to define which roles the user must have for access. The original identity system doesn't meet my requirements so I'm trying to create my own. I need a system that can be shared by multiple applications, but the original system seems to be focused on just one site. If that makes sense – SteinTheRuler Jan 13 '21 at 23:39
  • It seems what you want is somthing like Role-based authorization in ASP.NET Core?Evern no need custom any attribute,the default authorize attribute could limit the role access in Identity.If you must custom your own.You could see that my answer could get the user role successfully.Any question with my answer? – Rena Jan 14 '21 at 05:50
0

I ended up implementing an ActionFilterAttribute.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class SecureAttribute : ActionFilterAttribute
{
    private readonly string[] _roles;

    public SecureAttribute()
    {
        _roles = new string[0];
    }

    public SecureAttribute(params string[] roles)
    {
        _roles = roles;
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        ISecureRepo repo = (ISecureRepo)context.HttpContext.RequestServices.GetService(typeof(ISecureRepo));
        IUserProcessResult user = repo.GetCurrentUserAsync().Result;
        UnauthorizedObjectResult unauth = new UnauthorizedObjectResult($"{StatusCodes.Status401Unauthorized} Unauthorized");

        if (!user.Null())
        {
            bool access = true;

            foreach (string role in this.Roles) 
            { 
                if (user.User.Features.Where(ft => ft.Feature.Name == role).Count() == 0)
                {
                    access = false;
                }
            }

            if (!access)
            {
                context.Result = unauth;
            }
        }
        else
        {
            context.Result = unauth;
        }
    }

    public IEnumerable<string> Roles => _roles;
}
SteinTheRuler
  • 3,549
  • 4
  • 35
  • 71