Blazor Application Managing Authorization in a muli-tiered solution - c#

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.

Related

How can I secure Controllers in a Razor Class Library

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"));

EF Core reusable DbContext

I'm trying to create a reusable base for future web applications made with asp net core.
I created a library that contains a BaseDbContext that inherit from IdentityDbContext:
public class BaseDbContext : IdentityDbContext<ApplicationUser>
{
public BaseDbContext(DbContextOptions options) : base(options)
{
}
}
Inside this library there are some services for login and creation of Users.
Everytime that I will be creating a new WebApplication I will reference the library and I will create a new DbContext like this:
public class ProjectDbContext : BaseDbContext
{
//some generics DBSET
public ProjectDbContext (DbContextOptions<ProjectDbContext> options) : base(options)
{
}
}
And in the startup:
services.AddDbContext<ProjectDbContext>(options =>
{
options.UseSqlServer(connection);
});
Since the service for the login and creation of users require a reference of BaseDbContext, I created a IDbContextFactory inside the base project that will be implemented by the main project like this:
public class ProjectContextFactory : IDbContextFactory
{
private readonly ProjectDbContext _projectDbContext;
public ProjectDbContextFactory(ProjectDbContext remDbContext)
{
_remDbContext = remDbContext;
}
public BaseDbContext GetBaseDbContext()
{
return _projectDbContext;
}
}
This factory will be used inside the base project to get a reference to the BaseDbContext.
Is this a good thing to do? Can this create some kind of problems?
In general, no, this is not a good thing to do.
that will contains the entities that will be used for all web applications
If there's entities that are common to all projects, then those should be factored out completely. In other words, you'd have one project (your base project) with a context like UserContext, which will have your User and Credential entities, and then every other project would have its own separate context that deals with just what it needs. If the other application(s) need to access users, they'd either do so via an instance of UserContext or, better, through a service, such as an API.
That said, it sounds like you're rolling your own auth, which you should emphatically not do. Use Identity. And, if you need to share that between applications, you need a centralized auth provider, such as Identity Server.

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.

IdentityServer 4, Create Panel to CRUD Clients

Currently I Have configured Identityserver4 as separated project + My WebAPI and store in DB Credentials in IdentityServer.
Now i have problem how to make CRUD(In my frontend API) to IdentityServer(I want from my API add Clients to IdentityServer)
How to make property?
From IdentityServer4.EntityFramework and IdentityServer4.EntityFramework.Storage, you have access to IConfigurationDbContext (once you've added the required services in ConfigureServices using e.g. AddConfigurationStore). Because this is registered as part of the Dependency Injection system, you can take a dependency on it in one of your controllers. e.g.:
public class ClientsController : ControllerBase
{
private readonly IConfigurationDbContext _configurationDbContext;
public ClientsController(IConfigurationDbContext configurationDbContext)
{
_configurationDbContext = configurationDbContext;
}
// ...
}
IConfigurationDbContext is an abstraction of a standard DbContext, with the following DbSet<T> properties:
Clients
IdentityResources
ApiResources
It also includes both SaveChanges and SaveChangesAsync - Everything one might expect from a DbContext. Because of all of this, you can CRUD each of these entities just like any other Entity Framework Core driven database.
One final thing to note is that there are both Models (in IdentityServer4.Storage) and Entities (in IdentityServer4.EntityFramework.Storage). There are also a few extension methods for mapping between these (e.g. ClientMappers.ToEntity).
Given all of this, you can create a Model inside of your controller (or perhaps somewhere much better encapsulated than directly there). Here's a basic example for creating a new Client:
var clientModel = new Client
{
ClientId = "",
ClientName = "",
// ...
};
_configurationDbContext.Clients.Add(clientModel.ToEntity());
await _configurationDbContext.SaveChangesAsync();
The Client class here comes from IdentityServer4.Models and is then converted to an Entity using a ToEntity extension method I hinted at above. Working with a Model and converting to an Entity is simpler than trying to manipulate an Entity directly - If you're interested, you can see the mapping that takes place here.
This works in the same way for ApiResources, IdentityResources, etc. Use the source code links I've provided if you want to find out more about those specifically, but the information I've provided here should have you covered.
In order to use IdentityServer4 and IdentityServer4.EntityFramework in your API project, you can just add the two references to your API project. After that, you can configure the DI in the same way (using AddIdentityServer in ConfigureServices), but you don't need to add the middleware (using UseIdentityServer in Configure). You can even just use AddIdentityServer().AddConfigurationStore(...) to set up the relevant services, as you don't need a signing key, etc.
One way you can do this is by bootstrapping the ID4 Quickstart (tutorial located here):
http://docs.identityserver.io/en/release/quickstarts/3_interactive_login.html
Other option is to use their quickstart seeds located here to speed this up:
https://github.com/IdentityServer/IdentityServer4.Samples
Now if you want to implement restfull login there are constraints around it (i wanted to find out as well) check out this question:
IdentityServer 4 Restfull Login/Logout

Pass identity user to services

We are developing a web application in which the user can register orders, customers, etc. and later review them. We have services that are used by MVC controllers in order to interface with the web UI.
Now we face the problem of multiple users: each service should be provided the currently authorised user Id, so all operations (CRUD and bussiness logic) will only be allowed for that user id. How is it supposed to be passed?
I am thinking about having a parameter passed to my IDataService (base class for services), which is instantiated by the WhateverController, which in turn has access to the User.Identity.GetUserId() method, BUT as I am using an IoC container (Ninject) I don't know how to do that. I guess that IDataService needs a reference to a IUserInfoProvider, so it can call IUserInfoProvider.GetUserId(). Then I can inject somehow an implementation based on Identity and having the current web context information, pretty much in the same way that the Controller must be instantiated.
Question is: how to get that data?
A simpler solution, of course, would be to do it by hand in each Controller constructor, but there should be a more automatic and elegant way to solve this.
EDIT: After some more reasearch, thanks to the answer of Cuong Le, the question I had to ask was, in fact, "how to inject the UserManager from the current context?".
However, in order to decouple my services layer from MVC, I created an IUserInfoProvider, which provides access to the authenticated user data. The implementation based in Identity and the UserManager lies in the Web UI (MVC) project, so it has a IPrincipal as suggested by Cuong Le, and an ApplicationUserManager, all injected using Ninject.
The following interface abstract the user information from Identity and the UserManager.
public interface IUserInfoProvider<T>
{
string GetUserId();
T GetUserData();
}
Here is the implementation in the MVC project using Identity and UserManager.
public class IdentityUserInfoProvider : IUserInfoProvider<DatosEmpresa>
{
private readonly ApplicationUserManager _userManager;
private readonly IPrincipal _user;
public IdentityUserInfoProvider(ApplicationUserManager userManager, IPrincipal user)
{
_userManager = userManager;
_user = user;
}
public string GetUserId()
{
return _user.Identity.GetUserId();
}
public DatosEmpresa GetUserData()
{
return _userManager.FindById(_user.Identity.GetUserId()).DatosEmpresa;
}
}
And the Ninject configuration bit
kernel.Bind<IUserInfoProvider<DatosEmpresa>>().To<IdentityUserInfoProvider>();
kernel.Bind<IPrincipal>()
.ToMethod(ctx => HttpContext.Current.User)
.InRequestScope();
kernel.Bind<ApplicationUserManager>()
.ToMethod(ctx => HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>())
.InRequestScope();
Then I can use an IUserInfoProvider inside any service object and it gets the correct user.
The simple solution is you can put IPrincipal into NInject Container:
kernel.Bind<IPrincipal>().ToMethod(context => HttpContext.Current.User);
So in your ServiceBase you can inject IPrincipal via either property or contructor, like this:
class ServiceBase
{
[Inject]
public IPrincipal User { get; set; }
}
Now you can get information from this property.

Categories

Resources