we keep fighting with out multi tenant application.
This is an ASP MVC EF6 Code First web application.
We initialize a list of tenants in the Application_Start, getting a pair of values:
Host
TenantId
So we can associate any host with one TenantId, and store that list in cache.
We have configured a custom filter to get the current tenant.
public class TenantActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Items.Add("TenantId", GetCurrentTenant(filterContext.HttpContext.Request.Url.Host));
base.OnActionExecuting(filterContext);
}
}
The GetCurrentTenant function just access the list in cache and get the current one based on the host passed.
Is it correct to store the current tenant in an item in the context?
After that, we have created an Interceptor to get any query and add a filter to filter by TenantId. This is done and working good, we just need to add the tenantId from the context:
The problem we have is where we get the TenantId for each request.
if (HttpContext.Current.CurrentHandler == null) return;
var clientId = Convert.ToInt32(HttpContext.Current.Items["ClientId"]);
foreach (DbParameter param in command.Parameters)
{
if (param.ParameterName != TenantAwareAttribute.TenantIdFilterParameterName)
continue;
param.Value = clientId;
}
We don't know if this is the correct approach since there is a lot of informationon the net.
Thanks.
In my experience, the persistence of the tenant Id in the HTTP context is not right, as in some cases, the HTTP context becomes null.
You can try to get the tenant Id from the claims of the current principal. Creating a static class with a tenant identifier property that reads from the claims and gives is more reliable. Assuming you are using the owin pipeline, this should be easy to do. You can take a look at the reference sample application from github here
It looks like the below block,
public static class UserContext
{
public static string TenantId
{
get
{
return Threading.Thread.CurrentPrincipal.FindFirst("tenantid");
}
}
}
Related
I've created a Blazor Server App with the option to scaffold an identity system. This created an Entity Framework IdentityDbContext with a number of tables to manage user logins and settings. I decided to keep my own DbContext separate from this so that I could replace either of the contexts later, if necessary.
What I would like to do is have a User entity in my own custom dbcontext, and in it store a reference to the user id of the scaffolded IdentityDbContext entity. I would also like to ensure that I don't have to query the db for the custom entity every time the user opens a new page.
I've been looking around StackOverflow trying to find good suggestions of how to approach this, but I'm still not sure how to start. So I have a few questions:
Is my approach a sensible one?
How do I find a permanent id number or string to couple with on the UserIdentity?
Should I store my custom user entity in some sort of context so I don't have to query it all the time? If so, how?
All help is greatly appreciated!
It looks like your requirement is to store custom information about the current user above and beyond what is stored in Identity about the current user.
For simpler use cases you can create your own User class derived from IdentityUser and add additional properties on there and let Identity take care of all persistence and retrieval.
For more complex use cases you may follow the approach you have taken, whereby you create your own tables to store user related information.
It seems that you have taken the second approach.
Is my approach a sensible one?
I think so. Burying lots of business-specific context about the user in the Identity tables would tightly bind you to the Identity implementation.
How do I find a permanent id number or string to couple with on the
UserIdentity?
IdentityUser user = await UserManager<IdentityUser>.FindByNameAsync(username);
string uniqueId = user.Id;
// or, if the user is signed in ...
string uniqueId = UserManager<IdentityUser>.GetUserId(HttpContext.User);
Should I store my custom user entity in some sort of context so I
don't have to query it all the time? If so, how?
Let's say you have a class structure from your own DbContext that stores custom information about the user, then you can retrieve that when the user signs in, serialize it, and put it in a claim on the ClaimsPrincipal. This will then be available to you with every request without going back to the database. You can deserialize it from the Claims collection as needed and use it as required.
How to ...
Create a CustomUserClaimsPrincipalFactory (this will add custom claims when the user is authenticated by retrieving data from ICustomUserInfoService and storing in claims):
public class CustomUserClaimsPrincipalFactory
: UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
private readonly ICustomUserInfoService _customUserInfoService;
public CustomUserClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> optionsAccessor,
ICustomUserInfoService customUserInfoService)
: base(userManager, roleManager, optionsAccessor)
{
_customUserInfoService= customUserInfoService;
}
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(
ApplicationUser user)
{
var identity = await base.GenerateClaimsAsync(user);
MyCustomUserInfo customUserInfo =
await _customUserInfoService.GetInfoAsync();
// NOTE:
// ... to add more claims, the claim type need to be registered
// ... in StartUp.cs : ConfigureServices
// e.g
//services.AddIdentityServer()
// .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
// {
// options.IdentityResources["openid"].UserClaims.Add("role");
// options.ApiResources.Single().UserClaims.Add("role");
// options.IdentityResources["openid"].UserClaims.Add("my-custom-info");
// options.ApiResources.Single().UserClaims.Add("my-custom-info");
// });
List<Claim> claims = new List<Claim>
{
// Add serialized custom user info to claims
new Claim("my-custom-info", JsonSerializer.Serialize(customUserInfo))
};
identity.AddClaims(claims.ToArray());
return identity;
}
}
Register your CustomUserInfoService in Startup.cs (your own service to get your custom user info from the database):
services.AddScoped<ICustomUserInfoService>(_ => new CustomUserInfoService());
Register Identity Options (with your CustomUserClaimsPrincipalFactory and authorisation in Startup.cs. NOTE: addition of "my-custom-info" as a registered userclaim type. Without this your code in CustomUserInfoService will fail to add the claim type "my-custom-info":
services.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.User.RequireUniqueEmail = true;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddClaimsPrincipalFactory<CustomUserClaimsPrincipalFactory>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
options.IdentityResources["openid"].UserClaims.Add("my-custom-info");
options.ApiResources.Single().UserClaims.Add("my-custom-info");
});
You can then retrieve your custom user info from claims, without returning to database, by using:
MyCustomUserInfo customUserInfo =
JsonSerializer.Deserialize<MyCustomUserInfo>(
HttpContext.User.Claims
.SingleOrDefault(c => c.Type == "my-custom-info").Value);
I have a simple sandbox project I'm using to get to better understand how .net Core Identity works and I've come across a bit of an inconsistency that I hope someone can explain. This project is using Entity Framework.
I used this awesome article to help me set up the project, https://medium.com/#goodealsnow/asp-net-core-identity-3-0-6018fc151b4#.2env44446 and my User class is as follows.
public class User : IdentityUser<int>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string TempPassword { get; set; }
}
I seeded the db with three users and three roles, one user for each role, "Owner", "Admin", and "User". I added some policies for my actions,
auth.AddPolicy("Owner", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("Owner");
});
auth.AddPolicy("Admin", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("Admin", "Owner");
});
auth.AddPolicy("User", policy =>
{
policy.RequireAuthenticatedUser();
});
so my attributes like [Authorize("Admin")] work great. I even added some principal extensions as so
public static class PrincipalExtensions
{
public static bool IsOwner(this ClaimsPrincipal principal)
{
return principal.IsInRole("Owner");
}
public static bool IsAdmin(this ClaimsPrincipal principal)
{
return principal.IsInRole("Admin") || principal.IsInRole("Owner");
}
public static bool IsUser(this ClaimsPrincipal principal)
{
return principal.Identity.IsAuthenticated;
}
}
so I can do if(User.IsAdmin()) and this works perfectly as well.
Here is where it gets weird...
If I step through the following code I get confusing results.
var user = await _userManager.GetUserAsync(User);
var userRoles = await _userManager.GetRolesAsync(user);
await _userManager.AddToRoleAsync(user, "Owner");
The first line gets me a User object for the principal. On that object there is a collection of his Roles, user.Roles, but it will show empty (Count = 0) even though the user does have roles.
The second line gets the Roles for the user and it populates correctly.
The third line adds the "Owner" role to the user and it works correctly (the db is updated) but also, the local variable user suddenly now has that role in user.Roles! Note, none of the user's other roles will show up, just that one.
So I have basically two questions: 1. Why doesn't the user object have the user.Roles populated to begin with? 2. Why is it suddenly synced after I add a role?
Any help is appreciated.
Your Roles collection isn't populated after calling GetUserAsync() as the EntityFramework Identity UserStore doesn't request the information. It's doing the equivalent of you accessing the user data directly through your DbContext and without any calls to Include().
Right now EF Core does not support lazy loading, and therefore the user.Roles navigation property isn't automatically populated. And yes, this makes the behaviour somewhat disingenuous at the moment.
In your calls to GetRolesAsync() and AddToRoleAsync() the data is being explicitly populated for you, as you are operating on the roles directly.
I have implemented a CurrentUserPropertyBinder (see below) for a web application using FubuMVC.
public class CurrentUserPropertyBinder : IPropertyBinder
{
private readonly Database _database;
private readonly ISecurityContext _security;
public CurrentUserPropertyBinder(Database database, ISecurityContext security)
{
_database = database;
_security = security;
}
public bool Matches(PropertyInfo property)
{
return property.PropertyType == typeof(User)
&& property.Name == "CurrentUser";
}
public void Bind(PropertyInfo property, IBindingContext context)
{
var currentUser = //check database passing the username to get further user details using _security.CurrentIdentity.Name
property.SetValue(context.Object, currentUser, null);
}
}
When I login to my site, this works fine. The CurrentUserPropertyBinder has all the information it requires to perform the task (i.e. _security.CurrentIdentity.Name has the correct User details in it)
When I try and import a file using fineUploader (http://fineuploader.com/) which opens the standard fileDialog the _security.CurrentIdentity.Name is empty.
It doesn't seem to remember who the user was, I have no idea why. It works for all my other routes but then I import a file it will not remember the user.
Please help! Thanks in Advance
NOTE: We are using FubuMVC.Authentication to authenticate the users
I'm guessing your action for this is excluded from authentication; perhaps it's an AJAX-only endpoint/action. Without seeing what that action looks like, I think you can get away with a simple fix for this, if you've updated FubuMVC.Authentication in the past 3 months or so.
You need to enable pass-through authentication for this action. Out of the box, FubuMVC.Auth only wires up the IPrincipal for actions that require authentication. If you want access to that information from other actions, you have to enable the pass-through filter. Here are some quick ways to do that.
Adorn your endpoint/controller class, this specific action method, or the input model for this action with the [PassThroughAuthentication] attribute to opt-in to pass-through auth.
[PassThroughAuthentication]
public AjaxContinuation post_upload_file(UploadInputModel input) { ... }
or
[PassThroughAuthentication]
public class UploadInputModel { ... }
Alter the AuthenticationSettings to match the action call for pass-through in your FubuRegistry during bootstrap.
...
AlterSettings<AuthenticationSettings>(x => {
// Persistent cookie lasts 3 days ("remember me").
x.ExpireInMinutes = 4320;
// Many ways to filter here.
x.PassThroughChains.InputTypeIs<UploadInputModel>();
});
Check /_fubu/endpoints to ensure that the chain with your action call has the pass-through or authentication filter applied.
I am using Jersey Framework for developing my Webservices. I have a DTO object named UserInfo with setters and getters inside it. I am setting this DTO value initially when the user logs in. How can I pass this user-specific DTO to a Jersey service class?
I have tried setting them inside MultivaluedMap and Form but I was out of luck making it work.
This is my code :
MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
queryParams.add("queryTerm", "userdto");
Form f = new Form();
f.add("name", "1001D");
And this is the way I am trying to retrieve the Data.
public class HaiService {
#GET
#Produces("application/json")
#Consumes("application/json")
public String sayPlainTextHello(#Context UriInfo ui) {
MultivaluedMap queryParams=ui.getQueryParameters();
Iterator it=queryParams.keySet().iterator();
String theKey=null;
String returnString="";
while(it.hasNext()) {
theKey=(String)it.next();
System.out.println(queryParams.getFirst(theKey));
}
System.out.println("I am called");
return "Hi";
}
But I was out of luck. Typically my requirement is to store user-specific data on logon, and then retrive that inside the service class.
I am avoiding storing data in session because a user might login with multiple ID's under one browser, which produces the same session id, and there is a chance of data being overwritten for the first logged in user.
This is more like a conceptual question. When to use Model Binding (in ASP.NET MVC Framework) and when to inject objects using IoC (lets say Autofac here) ?
One specific scenario is like lets say, I have the following action method
public ActionResult EditProfile(string UserId)
{
// get user object from repository using the the UserId
// edit profile
// save changes
// return feedback
}
In the above scenario, is it possible to inject a user object to action method such that it automatically gets the user object using the UserId ? The resulting signature being:
public ActionResult EditProfile(UserProfile userObj) //userObj injected *somehow* to automatically retreive the object from repo using UserId ?
Sorry if it all doesn't makes sense. It`s my first time using IoC.
EDIT:
This is the way to do it > http://buildstarted.com/2010/09/12/custom-model-binders-in-mvc-3-with-imodelbinder/
You can do what you need using a custom action filter. By overriding OnActionExecuting, we have access to the route data, and the action parameters of the action that will be executed. Given:
public class BindUserProfileAttribute : ActionFilterAttribute
{
public override OnActionExecuting(FilterContext filterContext)
{
string id = (string)filterContext.RouteData.Values["UserId"];
var model = new UserProfile { Id = id };
filtextContext.ActionParameters["userObj"] = model;
}
}
This attribute allows us to create the parameters that will be passed into the action, so we can load the user object at this point.
[BindUserProfile]
public ActionResult EditProfile(UserProfile userObj)
{
}
You'll probably need to get specific with your routes:
routes.MapRoute(
"EditProfile",
"Account/EditProfile/{UserId}",
new { controller = "Account", action = "EditProfile" });
In MVC3 we get access to the new IDepedencyResolver interface, which allows us to perform IoC/SL using whatever IoC container or service locator we want, so we can push a service like a IUserProfileFactory into your filter, to then be able to create your UserProfile instance.
Hope that helps?
Model binding is used for your data. Dependency injection is used for your business logic.