How can I secure Controllers in a Razor Class Library - c#

I have developed some Admin functionality (EF logic, Controller, and Razor UI for Audit Logs actually) that I've packaged into a Razor Class Library (RCL) and created a NuGet package. I want this functionality available to users of the package, but I want to allow them to control the access security. I would usually decorate the Controller with an Authorize Attribute, something like:
[Area("MyAuditLogPackage")]
[Authorize(Roles = "Admin")]
public class AuditLogController : Controller
...
But I don't want to presume the client's security policy and Audit Logs are sensitive data.
They could derive their own controller from mine, but the original Route would still be in their default Area Mappings.
How can I give full control of this over to the package clients?

Rather than authorizing by Role, you could require that people using your code create custom security policies that are defined on startup. This would result in something like
[Area("MyAuditLogPackage")]
[Authorize(Policy= "AuditControllerPolicy")]
public class AuditLogController : Controller
...
The policy approach is extremely flexible so the policy might be a requirement that a user be in role Admin. It could also require other claims be present in the token, including custom claims. Check out Policy-based Authorization in Asp.Net Core.
This approach gives a user of your NuGet package complete flexibility, but many might find it burdensome. You might want to canvas a few to get their opinion first.

You can create a extension method to dynamically secure your Razor Class Library routes.
Definition:
internal static class IEndpointConventionBuilderExtensions
{
public static TBuilder AddAuthorization<TBuilder>(this TBuilder builder, AuthorizeAttribute? metadata = null)
where TBuilder : IEndpointConventionBuilder
{
if(metadata != null)
{
builder.WithMetadata(metadata);
}
return builder;
}
}
Usage:
app.MapControllerRoute(
name: "MasterData",
pattern: "{culture}/{area:exists}/{controller=Log}/{action=Index}/{dictionaryName?}/")
.AddAuthorization(new AuthorizeAttribute("MasterData"));

Related

Blazor Application Managing Authorization in a muli-tiered solution

I have a solution made up of a Blazor application (Server Side app with .Net 5.0) and a second project that is a Class Library (.Net Standard 2.0). The Blazor app handles all aspects of the website, including authentication. I moved all of my database CRUD procedures into the Class Library. The Blazor app authenticates and uses Roles/Claims to authorize different pages. But, how do I extend that authorization to my Class Library, so that I can use the same Class/Function decorations to designate authorized roles/claims? Here is an example of how my application is setup:
Blazor App - Startup.cs
The Blazor app has a reference to the Class Library and corresponding "Using statements". I then inject those classes into the ConfigureServices method, for use in pages.
public void ConfigureServices(IServiceCollection services)
{
//...adding other required services
// Inject class from my Class Library
services.AddTransient<ISqlDataAccess, SqlDataAccess >();
services.AddTransient<IPeopleData, PeopleData>();
}
Here is an example of how I use the injected classes, to pull data from my database. I can implement roles access here, but I also want to implement it in the Class library.
Somepage.Razor
#page "/mysite/people"
#attribute [Authorize(Roles = "admin")]
#using DataAccessLibrary
#using DataAccessLibrary.Models
#inject IPeopleData_db
//html to display data
#code{
//list of the returned data set, using the model, People
List<PeopleModel> people = new List<PeopleModel>();
protected override async Task OnInitializedAsync()
{
people = await _db.GetAllPeople();
}
}
DataAccessLibarary
IPeopleData.cs
public interface IPeopleData
{
Task<PeopleModel> GetAllPeople();
}
PeopleData.cs
public class PeopleData : IPeopleData
{
// ISqlDataAccess is a class that handles basic CRUD calls to the database. Uses Dapper easy object mapping
private readonly ISqlDataAccess _db;
public PeopleData(ISqlDataAccess db)
{
_db = db;
}
public Task<List<PersonModel>> GetAllPeople()
{
string sql = "select * from dbo.People";
return _db.LoadData<PersonModel, dynamic>(sql, new { });
}
}
So, I can add authorization attributes to the Blazor pages, using [Authorize(Roles = "admin")]. But, this does not protect the Class Library that manages all the database connection stuff. How can I extent the role based access control to that library, so that I can use similar decorations to my classes/functions, like [Authorize(Roles = "admin")]?
To answer your question directly: There is no mechanism for adding authorization to standard DotNetCore classes, which is what your library code is. Authorize is applied to components.
You need to structure your application applying "Separation of Concerns" principles to solve your problems.
You need to reconsider:
services.AddTransient<ISqlDataAccess, SqlDataAccess >();
It will almost certainly leak memory like a sieve. A SQL access service should be either Scoped or normally Singleton.

Sessionless controller in Asp.Net Mvc Core

I need to remove session from a controller because it adds unnecessarily load to my Redis server. I use Redis to store my session.
I have a controller that it's used from a Web-hook that its called rapidly and with high volume, the Web-hook doesn't use session and it would be better if I could completely remove the session from it.
As I google-searched I discover the attribute [ControllerSessionState] that removes the session from the controller but unfortunately it's only for Mvc3.
Is there something similar for Asp.Net Mvc Core?
There are two basic approaches
Middleware filter
Create a base controller from which your stateful controllers inherit from and decorate it with an middleware filter attribute which registers a session.
Once created you'd have a base class
public class SessionPipeline
{
public void Configure(IApplicationBuilder applicationBuilder)
{
applicationBuilder.UseSession();
}
}
[MiddlewareFilter(typeof(SessionPipeline))]
public class StatefulControllerBase : ControllerBase
{
}
and have your stateful Controllers inherit from StatefulControllerBase instead of ControllerBase/Controller
Use MapWhen to conditionally register the Session
This approach was more common in the first versions of ASP.NET Core 1.x, but isn't much used these days
app.MapWhen(context => !context.Request.Path.StartsWith("/hooks/"), branch =>
{
branch.UseSession();
});
This way session middleware will only be used for pathes not matching /hooks/ request path.

Authorization in ASP.net5

I am trying to see if there is something "out of the box" in ASP.net5 for authorization for my application needs. I am using a group/permission based approach for authorization. Using Identity3 I am using Role as Group and then I have created permissions from this. Each permission has a resource that it links to and 1 or more values, like:
Resource = Page, Permissions = Add, Update, View, Delete
Another complication is that the groups have dynamic names, and dynamic permissions!!
I have started to read about authorization in ASP.net5 and it seems that I have found something called Policies, which sound good. It seems to force you to use Claims, which is possible if I use a ClaimsTransformer to get all my permissions and add them as claims from the Db. But am I right in thinking that I would have to create a policy for each Permission, on each resource? That seems like a lot of setup.
Is there anything that I do not know about is already built in ASP.net5 that I could use? Like an attribute like this
[Authorize("Page", "Delete")]
Which I could add to the PageController Delete method.
If I have to use some sort of service and DI that into the controller to implement this, then that would be fine as well.
There is a ClaimsPrincipalPermissionAttribute that can fit to your requirements.
Or you can implement your own AuthorizeAttribute.
I use AspNet.Security.OpenIdConnect.Server for authorization. But you can also have a look at OpenIddict
In any case you can add the Authorize attribute to any method you want like this
[Authorize(Roles = "Administrator,SimpleUser,AnOtherRole")]
public void MyMethod() {}
Resource based authorization might fulfill your needs, but I am a little confused with the page being the resource, rather than what the page acts upon.
Taking your Page/Delete combination, I would imagine that rather than the resource being Page, your Page Delete action takes a parameter, indicating the page that is to be deleted? (If this is not the case then this approach isn't going to work of course)
In this case you'd do something like
[Authorize]
public class PageController : Controller
{
IAuthorizationService _authorizationService;
public PageController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public Delete(int pageId)
{
var page = pageRepo.GetPage(pageId);
if (await authorizationService.AuthorizeAsync(User, page, Operations.Delete))
{
return View(page);
}
else
{
return new ChallengeResult();
}
}
}
In order to enable this you're write a handler based on page and an Operations requirement (or any old requirement, but a parameterized operations requirement means you can write a single handler and branch accordingly).
We tried very hard to move away from putting data in the attribute, and move it into requirements, because data in attributes is, to be frank, a maintenance nightmare.
One other thing to note; as handlers are resolved through DI you could inject your user to permissions resolver into the handler, which would avoid using claims transformation.
ASP.NET provides authentication mechanism out of the box which is easy to use, example:
public class HomeController : Controller
{
[Authorize]
public ActionResult Index()
{
ViewBag.Message = "This can be viewed only by authenticated users only";
return View();
}
[Authorize(Roles="admin")]
public ActionResult AdminIndex()
{
ViewBag.Message = "This can be viewed only by users in Admin role only";
return View();
}
}
Check this tutorial
Or if you want more sophisticated mechanism you can implement your own memberhsip provider based on the ASP.NET Membership Provider

Blocking anonymous access by default in ASP .NET 5

My team and I are starting up a new website project in ASP .NET 5 and I'm trying to set up the basis of our user authentication and authorization policy.
So far, I've managed to use the [Authorize] and [AllowAnonymous] attributes to selectively define an authorization policy controllers or actions. The one thing I'm still struggling to achieve is defining a default authorization policy.
Bascially, I'd like every controller and action to behave as if they had an [Authorize] attribute by default, so that only actions specifically tagged as [AllowAnonymous] can be accessed by an anonymous user. Otherwise, we expect that, at some point, someone will forget to add an [Authorize] attribute to their controller and introduce vulnerabilities into the webapp.
It is my understanding that what I'm trying to do could be achieved in previous versions of ASP .NET by adding the following statement in FilterConfig.cs:
filters.Add(new AuthorizeAttribute());
... except that FilterConfig.cs no longer exists in MVC 6. According to How to register a global filter with mvc 6, asp.net 5 I can now access the global filters list using:
services.ConfigureMvc(options =>
{
options.Filters.Add(new YouGlobalActionFilter());
}
... tried it, looks fine, but now it's the AuthorizeAttribute filter that I can't seem to find.
For experimenting purposes I've tried to handcraft an equivalent to the AuthorizeAttribute filter and came up with the following:
public class LoginFilter: AuthorizeFilter
{
public LoginFilter(): base(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build())
{
}
public override Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.AuthorizationContext context)
{
if(!context.HttpContext.User.Identity.IsAuthenticated && context.ActionDescriptor is ControllerActionDescriptor)
{
var action = context.ActionDescriptor as ControllerActionDescriptor;
if(!AcceptAnonymous(action.ControllerTypeInfo) && !AcceptAnonymous(action.MethodInfo))
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
}
return base.OnAuthorizationAsync(context);
}
private static bool AcceptAnonymous(ICustomAttributeProvider o)
{
return o.IsDefined(typeof(AllowAnonymousAttribute), true);
}
}
This kinda works... I can add it to the global filters list, and it does reject queries coming from unauthenticated users unless the query is resolved to an action tagged [AllowsAnonymous].
However...
the AuthorizationPolicyBuilder thingy is ugly and misleading: it does not serve any purpose and is apparently ignored during the whole processing. The only reason I added it is that AuthorizeFilter requires an AuthorizationPolicy in its constructor. I guess, but haven't tried yet, that directly implementing IAsyncAuthorizationFilter would solve this particular issue
nothing in this code is specific to my webapp and the functionality was apparently provided in previous versions of the framework, so I'm willing to bet that there already is (or there will soon be) a component doing exactly the same thing, and I'd rather use a standard component from the framework than handcraft my own.
So, long story short, where has the AuthorizeAttribute filter gone? Or is there any functional equivalent I can use to make rejection of anonymous users the default behavior?
You can use Microsoft.AspNet.Mvc.AuthorizeFilter.
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Authorization;
services.ConfigureMvc(options =>
{
options.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()));
});
If you need custom authorization requirements see this answer for more information.

User (IPrincipal) not available on ApiController's constructor using Web Api 2.1 and Owin

I am Using Web Api 2.1 with Asp.Net Identity 2. I am trying to get the authenticated User on my ApiController's constructor (I am using AutoFac to inject my dependencies), but the User shows as not authenticated when the constructor is called.
I am trying to get the User so I can generate Audit information for any DB write-operations.
A few things I'm doing that can help on the diagnosis:
I am using only app.UseOAuthBearerTokens as authentication with Asp.Net Identity 2. This means that I removed the app.UseCookieAuthentication(new CookieAuthenticationOptions()) that comes enabled by default when you are creating a new Web Api 2.1 project with Asp.Net Identity 2.
Inside WebApiConfig I'm injecting my repository:
builder.RegisterType<ValueRepository>().As<IValueRepository>().InstancePerRequest();
Here's my controller:
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
private IValueRepository valueRepository;
public ValuesController(IValueRepository repo)
{
valueRepository = repo;
// I would need the User information here to pass it to my repository
// something like this:
valueRepository.SetUser(User);
}
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
// User is not avaliable here either...
}
}
But if I inspect the User object on the constructor, this is what I get:
The authentication is working, if I don't pass my token, it will respond with Unauthorized. If I pass the token and I try to access the user from any of the methods, it is authenticated and populated correctly. It just doesn't show up on the constructor when it is called.
In my WebApiConfig I am using:
public static void Register(HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// ... other unrelated injections using AutoFac
}
I noticed that if I remove this line: config.SuppressDefaultHostAuthentication() the User is populated on the constructor.
Is this expected? How can I get the authenticated user on the constructor?
EDIT:
As Rikard suggested I tried to get the user in the Initialize method, but it is still not available, giving me the same thing described in the image.
The problem lies indeed with config.SuppressDefaultHostAuthentication().
This article by Brock Allen nicely explains why that is. The method sets the principal intentionally to null so that default authentication like cookies do not work. Instead, the Web API Authentication filter then takes care of the authentication part.
Removing this configuration when you do not have cookie authentication could be an option.
A neat solution as mentioned here, is to scope the Web API parts of the application, so that you can separate out this configuration to a specific path only:
public void Configuration(IAppBuilder app)
{
var configuration = WebApiConfiguration.HttpConfiguration;
app.Map("/api", inner =>
{
inner.SuppressDefaultHostAuthentication();
// ...
inner.UseWebApi(configuration);
});
}
Don't know if this is still relevant, but I've had exactly the same problems, as you've described above. I've managed to solve it using custom OWIN middleware component.
Some info about my application structure:
Using MVC WebApp and WebAPI in same project (probably not the best option, but I have no time to change it, since deadline is approaching ;))
Using AutoFac as IoC container
Implemented custom ICurrentContext to hold information about currently logged on user (with CookieAuth in MVC and Bearer Token Auth in WebAPI), which is injected where needed (controllers, BAL objects, etc.)
Using EntityFramework 6 for Db access
Converted ASP.NET Identity to use int keys rather than string (http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity)
So on to the code. This is my ICurrentContext interface:
public interface ICurrentContext
{
User CurrentUser { get; set; } // User is my User class which holds some user properties
int? CurrentUserId { get; }
}
and implementation of it:
public class DefaultCurrentContext : ICurrentContext
{
public User CurrentUser { get; set; }
public int? CurrentUserId { get { return User != null ? CurrentUser.Id : (int?)null; } }
}
I've also created an OWIN middleware component:
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
namespace MyWebApp.Web.AppCode.MiddlewareOwin
{
public class WebApiAuthInfoMiddleware : OwinMiddleware
{
public WebApiAuthInfoMiddleware(OwinMiddleware next)
: base(next)
{
}
public override Task Invoke(IOwinContext context)
{
var userId = context.Request.User.Identity.GetUserId<int>();
context.Environment[MyWebApp.Constants.Constant.WebApiCurrentUserId] = userId;
return Next.Invoke(context);
}
}
}
Some information about this component: MyWebApp.Constants.Constant.WebApiCurrentUserId is some string constant (you can use your own) that I've used to avoid typos since its used in more than one place. Basicly what this middleware does, is that it adds current UserId to the OWIN environment dictionary and then Invokes the next action in pipeline.
Then I've created Use* extension statement to include OMC (OWIN Middleware Component) into OWIN pipeline:
using System;
using Owin;
namespace MyWebApp.Web.AppCode.MiddlewareOwin
{
public static class OwinAppBuilderExtensions
{
public static IAppBuilder UseWebApiAuthInfo(this IAppBuilder #this)
{
if (#this == null)
{
throw new ArgumentNullException("app");
}
#this.Use(typeof(WebApiAuthInfoMiddleware));
return #this;
}
}
}
To use this OMC, I've put the Use* statement right after Use* statement for Bearer token inside my Startup.Auth.cs:
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions); // This was here before
// Register AuthInfo to retrieve UserId before executing of Api controllers
app.UseWebApiAuthInfo(); // Use newly created OMC
Now the actual usage of this principle was inside AutoFac's Register method (called on some bootstrap code at the start of web application; in my case this was inside Startup class (Startup.cs), Configuration method) for my ICurrentContext implementation which is:
private static void RegisterCurrentContext(ContainerBuilder builder)
{
// Register current context
builder.Register(c =>
{
// Try to get User's Id first from Identity of HttpContext.Current
var appUserId = HttpContext.Current.User.Identity.GetUserId<int>();
// If appUserId is still zero, try to get it from Owin.Enviroment where WebApiAuthInfo middleware components puts it.
if (appUserId <= 0)
{
object appUserIdObj;
var env = HttpContext.Current.GetOwinContext().Environment;
if (env.TryGetValue(MyWebApp.Constants.Constant.WebApiCurrentUserId, out appUserIdObj))
{
appUserId = (int)appUserIdObj;
}
}
// WORK: Read user from database based on appUserId and create appUser object.
return new DefaultCurrentContext
{
CurrentUser = appUser,
};
}).As<ICurrentContext>().InstancePerLifetimeScope();
}
This method is called where I build AutoFac's container (hence the input parameter of type ContainerBuilder).
This way I got single implementation of CurrentContext, no matter how user was authenticated (via MVC Web Application or Web API). Web API calls in my case were made from some desktop application, but database and most of codebase were the same for MVC App and Web API.
Don't know if its the right way to go, but it has worked for me. Although I am still a little concerned how would this behave thread-wise, since I don't know exactly how using HttpContext.Current inside API calls would behave. I've read somewhere that OWIN Dictionary is used per-request basis, so I think this is safe approach. And I also think that this isn't so neat code, but rather a little nasty hack to read UserId. ;) If there's anything wrong with using this approcah, I'd appreciate any comment regarding it. I've been strugling with this for two weeks now and this is the closest I got of getting UserId in one place (when resolving ICurrentContext from AutoFac through lambda).
NOTE: Wherever there is usage of GetUserId, it can be replaced with original GetUserId (which returns string) implementation. The reason I'm using GetUserId is because I've rewritten ASP.NET to some extent for using ints instead of strings for TKey. I've done this based on following article: http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity
The User property of the controller is not populated until the Initialize method is called which happens after the constructor is invoked, hence thats why the Identity is not yet populated with the authorzied user data.
I realized that removing config.SuppressDefaultHostAuthentication() allowed me to get the Identity in the constructor much earlier. However, I wouldnt suggest doing this if you are using Token Authentication.
Thread.CurrentPrincipical is available throughout the pipeline, you could skip the User registration below:
valueRepository.SetUser(User);
and access
Thread.CurrentPrincipical
In the repository instead, making the repository context aware. Furthermore, you could add a context layer.
If nothing of the above solutions work try this one:
public ActionResult GetFiles()
{
...
string domainID = System.Web.HttpContext.Current.Request.LogonUserIdentity.Name;
...
}

Categories

Resources