Create a deny role attribute in .netcore 5.0 - asp.net-core

I want to create an attribute based on the authorize attribute that instead of granting a role access to an IActionResult it denies access.
I want to decorate the ActionResult with something like [Deny(Role="role")] so that if the role tries to access this it is redirected back to the refering url.
I have tried to modify the code below but a lot of the methods used do not exist in .netcore 5.0:
public class DenyAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return !base.AuthorizeCore(httpContext);
}
}
Or
public class DenyByControllerActionAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var controller = httpContext.Request.RequestContext.RouteData.GetRequiredString("controller");
var action = httpContext.Request.RequestContext.RouteData.GetRequiredString("action");
var denyRole = string.Format("Deny{0}:{1}", controller, action);
return !httpContext.User.IsInRole(denyRole) && base.AuthorizeCore(httpContext);
}
}
How would go about creating something like the above code examples in .netcore 5.0 since the AuthorizeCore override no longer exist in .net 5.0?

In ASP.NET Core, you need implements Attribute and IAuthorizationFilter to custom authorize attribute:
public class DenyAttribute : Attribute, IAuthorizationFilter
{
public string? Roles { get; set; }
public void OnAuthorization(AuthorizationFilterContext context)
{
var originalUrl = context.HttpContext.Request.Headers["Referer"].ToString();
var userInRole = context.HttpContext.User.IsInRole(Roles);
if(userInRole)
{
context.Result = new RedirectResult(originalUrl);
}
}
}
Controller:
[Deny(Roles = "yourRole")]
public IActionResult Test()
{
return View();
}

Related

How to get IdentityServer to work with AddDbContextPool

I'm trying to leverage AddDBContextPool in my ASP Core API project that uses IdentityServer for authentication. The problem is, to use PersistedGrants you need to have the DBContext set up this way:
private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
public ApplicationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options)
{
_operationalStoreOptions = operationalStoreOptions;
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
}
AddDBContextPool requires only a single constructor that takes only DbContextOptions as a parameter. So I can't inject operationalStoreOptions anymore.
I tried using AddOperationalStore() from the Startup class, but I got the following error:
The entity type 'DeviceFlowCodes' requires a primary key to be
defined. If you intended to use a keyless entity type call
'HasNoKey()'.
Any idea what am I missing?
I ended up manually implementing the IPersistedGrantDbContext interface in my class. The code is below in case this could help anyone else.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>, IPersistedGrantDbContext
{
public ApplicationDbContext(DbContextOptions options) : base(options)
{
// No tracking needed as all transactions are disconnected
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
public DbSet<PersistedGrant> PersistedGrants { get; set; } = null!;
public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; } = null!;
public Task<int> SaveChangesAsync()
{
return base.SaveChangesAsync();
}
protected override void OnModelCreating(ModelBuilder builder)
{
OperationalStoreOptions options = new OperationalStoreOptions
{
DeviceFlowCodes = new TableConfiguration("DeviceCodes"),
PersistedGrants = new TableConfiguration("PersistedGrants")
};
base.OnModelCreating(builder);
builder.ConfigurePersistedGrantContext(options);
// ... Rest of configuration here
}
}

Unable to get custom attributes in asp.net action filter

I have created a custom attribute and I am trying to retrieve the value of this custom attribute in asp.net action filter but it seems to be unavailable. What am I doing wrong?
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public sealed class MyCustomAttribute : Attribute
{
MyCustomAttribute(string param)
{
}
}
public class MyCustomActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
throw new NotImplementedException();
}
public void OnActionExecuting(ActionExecutingContext context)
{
// unable to find my custom attribute here under context.Filters or anywhere else.
}
}
[HttpGet]
[MyCustomAttribute ("test123")]
public async Task<Details> GetDetails(
{
}
What you want to achieve is a little more complicated if you want to do it yourself (ie. reflecting attribute value from method of Controller).
I would recommend using built-in attribute filters from ASP.NET Core (more in ASP.NET Core documentation), in your example:
public class MyCustomActionAttribute : ActionFilterAttribute
{
private readonly string param;
public MyCustomActionAttribute(string param)
{
this.param = param;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var paramValue = param;
base.OnActionExecuting(context);
}
}
and annotating your controller action like this:
[HttpGet]
[MyCustomAction("test123")]
public async Task<Details> GetDetails()
{
}

Convert HttpContext.User before API Method

I currently have a .Net Core API application with a bunch of API get methods. Currently in every single method I am needing to write this line:
[ProducesResponseType(200, Type = typeof(MetadataAttributeModel))]
[ProducesResponseType(400, Type = typeof(ValidationResultModel))]
[ProducesResponseType(500, Type = typeof(ErrorResultModel))]
public ActionResult<MetadataAttributeModel> GetAsync(string name)
{
List<Entities.DocumentAttributeView> attributes = documentAttributeViewRepo.GetByAttributeName(name);
SiteUser currentUser = new SiteUser(db, User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
return Unauthorized();
}
Is there a way I can convert the HttpContext.User object to our own SiteUser object before I get to the method? I don't want to have to write this line in ALL of the API methods:
SiteUser currentUser = new SiteUser(db, HttpContext.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
TIA,
Alex
The AspNet Mvc mechanism for "Do something for every Action" is Filters.
Filters can run before the method is called, and they can, for instance, set the Http.Context.User.
A filter can be applied to an action, a controller, or (by writing code in Startup) globally.
[SwapUserToAuthorizedDatabaseUser]
public class MyController
{
public IActionResult About() => Ok(User);
}
Which will invoke this filter for every Action on the Controller :
public class SwapUserToAuthorizedDatabaseUserAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
SiteUser currentUser = new SiteUser(db, User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
if (currentUser == null)
{
context.Result= new RedirectToRouteResult("/Identity/Logout");
}
else
{
var claimsIdentity =
new ClaimsIdentity(
new Claim[]
{
new Claim("Id", currentUser.Id),
new Claim("UserName", currentUser.UserName),
new Claim("WhateverElseYourSiteUserHas", currentUser.Something.ToString()),
}
);
context.HttpContext.User = new ClaimsPrincipal(new[]{claimsIdentity});
}
}
public void OnActionExecuted(ActionExecutedContext context){}
}
If overwriting the HttpContext.User isn't what you need, then it's much less code to use HttpContext.Items :
public class SwapUserToAuthorizedDatabaseUserAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Items["SiteUser"]= new SiteUser(db, User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
}
public void OnActionExecuted(ActionExecutedContext context){}
}
Instead of an IActionFilter to run on every Action, you can use an IAuthorizationFilter which has a public void OnAuthorization(AuthorizationFilterContext context) method. This would save repeatedly calling the database, but does mean you must cache your currentUser somewhere, presumably in Session.
The problem is, how do you get access to your database? If you go the route of adding a Global filter by adding it in Startup:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc(o=>o.Filters.Add(new SwapUserToAuthorizedDatabaseUserAttribute(provide a db instance here)));
}
Then you can give your Filter a constructor and pass in a database. There's also an overload for using the DependencyInjection system.
If you don't use the startup method, you have to do some DIY injection, for instance by having a static method to return a DbContext.
You can move this logic to a service:
public class UserService : IUserService
{
private readonly HttpContext context;
private readonly Db db;
public UserService(IHttpContextAccessor context, Db db)
{
this.context = context.HttpContext;
this.db = db;
}
public SiteUser GetUser()
{
return new SiteUser(db, context.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
}
}
Inject it to controllers where it's required:
public MyController(IUserService userService) { ... }
Register it as a Scoped service in ConfigureServices in Startup.cs along with IHttpContextAccessor (should be singleton):
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<UserService>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

Request.IsAuthenticated function in ASP.NET 5

Is there an equivalent to Request.IsAuthenticated in ASP.NET 5 hidden somewhere or are we expected to loop through the user's identities and determine this ourselves?
If you just need to know if the User object is authenticated, this property should do the trick:
User.Identity.IsAuthenticated
If you need to prevent an action from being called by an unauthenticated user, the following attribute class works great.
public class BasicAuthAttribute : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
var user = filterContext.HttpContext.User;
if (user == null || !user.Identity.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
I use this in my base controller class as follows.
[BasicAuth]
public abstract class BaseAuthorizedController : Controller
You can also access the IsAuthenticated property from within your service layer by injecting an IHttpContextAccessor into it, like this:
public class MyService : IMyService {
private readonly IHttpContextAccessor httpContextAccesor;
public MyService(IHttpContextAccessor httpContextAccessor) {
this.httpContextAccessor = httpContextAccessor;
}
public void MyMethod() {
var isAuthenticated = this.httpContextAccessor.HttpContext.User.Identity.IsAuthenticated;
if (isAuthenticated) {
// Authenticated, do something!
}
}
}

Using MVC 4 & WebAPI, how do I redirect to an alternate service endpoint from within a custom filter?

Thanks for looking.
This is a trivial task when using a normal (not WebAPI) action filter as I can just alter the filterContext.Result property like so:
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary { { "controller", "Home" }, {"action", "Index" } });
Unfortunately, I have to use HttpActionContext for WebAPI, so I can not access filterContext.Result.
So what should I do in place of that? I have the filter set up and it does execute at the appropriate time, I just don't know how to make it prevent execution of the requested service endpoint and instead point to a different one.
Here is my controller:
[VerifyToken]
public class ProductController : ApiController
{
#region Public
public List<DAL.Product.CategoryModel> ProductCategories(GenericTokenModel req)
{
return HelperMethods.Cacheable(BLL.Product.GetProductCategories, "AllCategories");
}
public string Error() //This is the endpoint I would like to reach from the filter!
{
return "Not Authorized";
}
#endregion Public
#region Models
public class GenericTokenModel
{
public string Token { get; set; }
}
#endregion Models
}
Here is my filter:
using System.Web.Http.Controllers;
using ActionFilterAttribute = System.Web.Http.Filters.ActionFilterAttribute;
namespace Web.Filters
{
public class VerifyTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
dynamic test = filterContext.ActionArguments["req"];
if (test.Token != "foo")
{
//How do I redirect from here??
}
base.OnActionExecuting(filterContext);
}
}
}
Any help is appreciated.
The answer in my case was simply to change the Response property of the filterContext rather than to redirect to a different endpoint. This achieved the desired result.
Here is the revised filter:
public class VerifyTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
dynamic test = filterContext.ActionArguments["req"];
if (test.Token != "foo")
{
filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
base.OnActionExecuting(filterContext);
}
}