Does the multi-user application allow one user to access many tenant accounts?

I am writing a multi-user application using one database for each tenant model. I allowed each user account to access multiple tenants (as long as the tenant granted them access)

Each page sent to the browser includes the current TenantId in Site.Master

<%= Html.Hidden("TenantId") %> 

But when any request is made from the browser (submit button, AJAX GET or AJAX POST), this TenantId is not actually checked if it matches the current TenantId.

Now, if the user opens one tab using TenantId = 1, then connects to another tenant on another tab with TenantId = 2, then switches to the first tab and has access to data from Tenant 2.

What can I do to fix this? I have a large number of existing ActionResult and JsonResult methods, and I do not want to go through each of them and add

 if (request.TenantId != user.CurrentTenantId) return false 

Because it will be a large amount of duplication of effort

Can I change the base controller to always read the TenantId value? This may work for submitted requests (ActionResult), but what about AJAX requests?

How can I check TenantId on a page inside JsonResult actions without changing every existing AJAX method (there are many of them)?

+6
source share
4 answers

You can check your event in the Application_Request application in the Global.asax.cs file. If what you need is populated using the MVC model binding, then maybe write your own ActionFilter to test it and register it with all the actions through GlobalFilter.

+2
source

You can write your own filter:

How to get specific code to execute before each action of one controller in ASP.NET MVC 2? Code execution before any action

Of course, there is no ready-made answer to your question. You need to write your own logic on how to handle tenantId. For example, for each action, check to see if it matches the current session tenant ID. Or put it in a cookie and each time check if id are equal. It is for you. From my point of view, cookie is more preferable. But it feeds on traffic.

0
source

If I understood correctly, users could simultaneously open 2 different tabs, each of which would be a different tenant. And each page should display data related to each tenant.

Thus, this means that the decision associated with the cookie or session should be discarded as the tenant belongs to each tab of the browser.

And, reading your response to Kirill Gupta’s suggestion, I understand that a hidden tenantId on every page cannot be sent to every AJAX request. Of course, one solution could be to modify your application and make sure that this always applies to every AJAX request. Otherwise, it would also cancel the global filter based on the request parameters, since tenantId may not always be there.

I think the best option is to add a segment to the url containing tenantId. For example, replacing the default route with something like the following route (if you have many different routes, you need to be very careful to avoid colliding with the route):

 routes.MapRoute( name: "Default", url: "{tenant}/{controller}/{action}/{id}", defaults: new { tenant = "defaultTenant", controller = "Home", action = "Index", id = UrlParameter.Optional } ); 

Thus, you can make sure that the tenant will always be sent for each request, and you can also have 2 different tabs with different tenants displaying each corresponding file.

There are various options for restoring the value of a route segment.

The binding will automatically fill in the value for any parameter with the name "tenant" of your action method or any parameter with the name "tenant" in the model class, which is the parameter of the action method: public ActionResult Foo (model FooModel, tenant of strings) {// as a tenant, so and model.tenant will contain the value of the segment URL return View (); }

You can also write a filter that accesses the value of the route parameter (RouteData is a property of the ActionExecutingContext and ActionExecutedContext , which are accepted as parameters of filter methods), and performs some logic. Then the filter will be installed as a global filter in your application or on your base controller:

 public class FooFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var tenant = filterContext.RouteData.Values["tenant"] //do whatever you need to do before executing the action, based on the tenant } public override void OnActionExecuted(ActionExecutedContext filterContext) { var tenant = filterContext.RouteData.Values["tenant"] //do whatever you need to do after executing the action, based on the tenant } } 

The final parameter is direct access to the RouteData parameter in your base controller class. (Since RouteData is a property of the MVC Controller base class)

As long as you use the Html and Ajax helpers to create the URLs, the tenant segment of the URL will be supported in your links. However, if you have jquery code that directly sends AJAX calls with hard-coded URLs, you will need to update that code so that the new URL segment is taken into account.

Finally, if the tenantId values ​​are not very user friendly, like an integer, you can have unique names for each tenant and use the names in the URL. Then you would add some logic that maps it to the integer value your application needs.

0
source

You can apply a filter at the controller level and check the transmitted tenantid. Controller level filters should not be more complex than action filters. For my project, I needed to check the authorization in the same way, but I tried the controller class, and then inherited from my own controller class, as I had some special requirements.

Where are you going to store the tenant identifier on the client side? It seems to me that you should use a session object for this.

-1
source

All Articles