How to get Identity User outside Razor Pages in Blazor Server-side? - asp.net

I am working on a Blazor Server-Side application, using Microsoft Identity, Entity Framework and a multitenant approach with shared Db.
I have extended the IdentityUser class so that I could have the TenantId in the AspNetUser Table
public class ApplicationUser : IdentityUser
{
public int TenantId { get; set; }
}
}
Then I have applied a general query filter to my dbModel based on the TenantId
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Employee>().HasQueryFilter(a => a.TenantId == TenantId);
}
In my blazor page I can call this function
public async Task SetTenant()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
ApplicationUser = await UserManager.FindByNameAsync(user.Identity.Name);
var TenatId = ApplicationUser.TenantId;
}
Finally in my service I can get a list of Employees with the right TenantId
public Task<Employee[]> GetEmployees(int TenatntID)
{
using (var ctx = new ProgramDbContext(TenantId))
{
return Task.FromResult(ctx.Employee.Select(d => new Employee
{
Id = d.Id,
TenantId = d.TenantId,
Name= d.Name,
}).ToArray());
}
}
With this approach, everytime I want to call a function to get DB's Data, I need to identity the user and get the TenantId, then call the specific function and pass the tenantID to it.
I would like to know if my approach is completely wrong to implement this type of solution, for example:
Is it possible to add a Singleton service of an ApplicationUser, so that once is is identified after login, i can inject the service in every class where i need the ApplicationUser.TenantId?
Is it possible to identify and authenticate the Application User outside a blazor class? for example a plain C# class? I was able to pass the AuthenticationStateProvider and UserManager in the constructor of my Service class, but I cant await a function inside the constructor to actually get the ApplicationUser object.
public CaronteWebService(AuthenticationStateProvider authenticationStateProvider, UserManager userManager)
{
_AuthenticationStateProvider = authenticationStateProvider;
_userManager = userManager;
}
UserManager<ApplicationUser> _userManager;
public ApplicationUser ApplicationUser { get; set; }
AuthenticationStateProvider _AuthenticationStateProvider { get; set; }

Related

ASP.NET MVC 5 + EF6 + Ninject - Multitenancy Database

I have a business ASP.NET MVC5 application where each customer has his own database. I want to use EF6 and Ninject for DI. For login I'm using ASP.NET Identity.
For each user exists a UserClaim where the name of the database is specified:
UserId = 1 | ClaimType = "db_name" | ClaimValue = "Customer0001"
UserId = 2 | ClaimType = "db_name" | ClaimValue = "Customer0002"
and so on... This means it is one web-application with a "shared" database for user authentication and on the other side each customers has his own database - all databases are located on the same database server (MS SQL Server).
The user need to login in, after login he should receive data from his personal database (specified in the UserClaim-Table).
For Ninject I think I have to something like this
private void AddBindings() {
kernel.Bind<EFDBContext>().ToMethod(c => new EFDBContext("db_name"));
}
But how would I get the UserClaim into the bindings? (I don't want to use a Session, because sessions can get lost).
And what steps after the bindings are necessary?
For example at the AccountRepository the EFDBContext expects the "db_name" > but how would I get it there?
public class AccountRepository : IAccountRepository {
private EFDBContext context = new EFDBContext("db_name");
}
And finally I can change the connection string inside of this class??
public class EFDBContext : DbContext {
public EFDBContext(string db_name) : base("EFDBContext") {
}
}
UPDATE AFTER #Hooman Bahreini ANSWER
NinjectDependencieResolver.cs
private void AddBindings() {
kernel.Bind<ICustomerRepository>().To<CustomerRepository>().WithConstructorArgument("http_current_context", HttpContext.Current);
}
CustomerRepository.cs
public class CustomerRepository : ICustomerRepository {
private CustomerDBContext context;
public CustomerRepository(HttpContext httpContext) {
string db_name = "";
var claimValue = ((ClaimsPrincipal)HttpContext.Current.User).Claims.FirstOrDefault(c => c.Type == "db_name");
if(claimValue != null) {
db_name = claimValue.Value.ToString();
}
context = new CustomerDBContext(db_name);
}
public IEnumerable<Test> Tests {
get { return context.Test; }
}
}
DB-Context-File
public class CustomerDBContext : DbContext {
public CustomerDBContext(string db_name) : base("CustomerDBContext") {
string temp_connection = Database.Connection.ConnectionString.Replace(";Initial Catalog=;", ";Initial Catalog=" + db_name + ";");
Database.Connection.ConnectionString = temp_connection;
}
public DbSet<Test> Test { get; set; }
}
You can access user claims from HttpContext:
var claimValue = ((ClaimsPrincipal)HttpContext.Current.User)
.Claims
.FirstOrDefault(c => c.Type == "db_name");
For your ninject code, you can create an extension method for HttpContext:
public static HttpcontextExtensions
{
public static string GetDbName(this HttpContext context)
{
return ((ClaimsPrincipal)context.Current.User)
.Claims
.FirstOrDefault(c => c.Type == "db_name");
}
}
And use the following ninject binding:
kernel.Bind<ICustomerRepository>()
.To<CustomerRepository>()
.WithConstructorArgument("db_name", HttpContext.GetDbName());
See this document for more info about accessing HttpContext in ninject.
In your example, CustomerRepository has a dependency on HttpContext, this is not a good design. CustomerRepository requires a db-name, and that's what should be passed in the constructor. Related to this is Nikola’s 4th law of IoC
Every constructor of a class being resolved should not have any
implementation other than accepting a set of its own dependencies.
To give you an example, you don't have any HttpContext in your test project, which makes unit testing CustomerRepository complicated.
P.S. I don't know your design, but maybe getting db-name from HttpContext is not an ideal solution... user may logout or clear their browser history and you will loose your db-name.

Asp.net Identity DbContext / Repository Issue

I am using Asp.Net identity within my MVC app. I can see that this has it's own ApplicationDbContext - albeit it is connected to the same SQL db as my own DbContext I am using elsewhere.
So I am trying to access some of my own data via my own code within the AccountController - it does not seem to work I presume because of some confusion over which DBContext it thinks is active?
My Code :
public class AccountController : Controller
{
private ApplicationSignInManager _signInManager;
private ApplicationUserManager _userManager;
private PostageManager postmgr;
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager, PostageManager _postmgr)
{
UserManager = userManager;
SignInManager = signInManager;
postmgr = _postmgr;
}
public ApplicationSignInManager SignInManager
{
get
{
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
private set
{
_signInManager = value;
}
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
// GET: /Account/Register
[AllowAnonymous]
public ActionResult Register()
{
//create select list items for countries drop down
List<SelectListItem> countries;
countries = postmgr.GetCountries().Select(item => new SelectListItem
{
Value = item.Country,
Text = item.Country
}).ToList();
countries.Insert(0, new SelectListItem { Value = string.Empty, Text = "Select delivery country or region...", Selected = true });
RegisterViewModel mode = new RegisterViewModel
{
Countries = countries
};
return View();
}
}
}
PostageManager is just a class that sits over my DAL to fetch some data (which uses repository pattern) - I'm using just a kind of pass through method to grab a list of countries, and using it in exactly the same way I have in other controllers which works fine. Underneath that class is my repository code that is linked to my default connection string (DBContext). It's balking at the following line with a null reference exception, I think postmgr is null :
countries = postmgr.GetCountries().Select(item => new SelectListItem
In reverse to get access to the identity data within my own controllers I have done the following :
public BasketController(BasketManager _mgr, PostageManager _postmgr, ProductManager _prodmgr)
{
mgr = _mgr;
postmgr = _postmgr;
prodmgr = _prodmgr;
shopper = Cart.GetShopperId();
this.applicationDbContext = new ApplicationDbContext();
this.userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(this.applicationDbContext));
}
protected ApplicationDbContext applicationDbContext { get; set; }
protected UserManager<ApplicationUser> userManager { get; set; }
Which as far as I understand it points the identity code to use the right DbContext - I looked at doing this in reverse in my AccountController but can't fathom it out.
I basically just want to be able to use my own code that grabs my own data from within the Identity controllers to help pass extra data etc through to the views.
I might be wrong but most probably postmgr field is not initialized from constructor and that is why you have this error.
Explanation:
By default Asp will try to create controller instance by constructor without parameters. If Asp can't find constructor without parameters it will try to call constructor with parameters, but to make it possible you have to configure IoC in your app. As your controler has constructor without parameters it will be selected by Asp. So all 3 fields are empty.
But in properties SignInManager and UserManager you try to take value from field or from OwinContext. As field is empty your code will take value from OwinContext. OwinContext is quite complex and smart tool that create its context automatically based on configuration provided in Startup.Auth.cs file or any other file under App_Start folder.
I think I have figured it out - added the following to my NinjectControllerFactory :
ninjectKernel.Bind<IAuthenticationManager>().ToMethod(c => HttpContext.Current.GetOwinContext().Authentication); //.InRequestScope();
ninjectKernel.Bind<IUserStore<ApplicationUser>>().To<UserStore<ApplicationUser>>();
ninjectKernel.Bind<UserManager<ApplicationUser>>().ToSelf();
ninjectKernel.Bind<IRoleStore<IdentityRole, string>>().To<RoleStore<IdentityRole, string, IdentityUserRole>>();
ninjectKernel.Bind<RoleManager<IdentityRole>>().ToSelf();
And changed my constructor to :
public AccountController(PostageManager _postmgr)
{
postmgr = _postmgr;
}

Use WFC service calls as UserStore for ASP.NET Identity

I am creating a web forms application that uses a WCF service to interact with the database and other applications. This web forms application has no access to the database.
I would like to use ASP.Net Identity for user management. I have already created a custom UserStore and RoleStore by following this tutorial, Overview of Custom Storage Providers for ASP.NET Identity, as shown below.
public class UserStore : IUserStore<IdentityUser, long>, IUserRoleStore<IdentityUser, long>
{
UserServiceClient userServiceClient = new UserServiceClient();
public Task CreateAsync(IdentityUser user)
{
string userName = HttpContext.Current.User.Identity.GetUserName();
Genders gender = (Genders)user.CoreUser.Gender.GenderId;
UserDto userDto = userServiceClient.CreateUser(user.CoreUser.FirstName, user.CoreUser.LastName, gender, user.CoreUser.EmailAddress, user.CoreUser.Username, userName, user.CoreUser.Msisdn);
return Task.FromResult<UserDto>(userDto);
}
public Task DeleteAsync(IdentityUser user)
{
bool success = userServiceClient.DeactivateUser(user.CoreUser.UserId, "");
return Task.FromResult<bool>(success);
}
public Task<IdentityUser> FindByIdAsync(long userId)
{
UserDto userDto = userServiceClient.GetUserByUserId(userId);
return Task.FromResult<IdentityUser>(new IdentityUser { CoreUser = userDto, UserName = userDto.Username });
}
public Task<IdentityUser> FindByNameAsync(string userName)
{
UserDto userDto = userServiceClient.GetUserByUsername(userName);
return Task.FromResult<IdentityUser>(new IdentityUser { CoreUser = userDto, UserName = userDto.Username });
}
public Task UpdateAsync(IdentityUser user)
{
Genders gender = (Genders)user.CoreUser.Gender.GenderId;
UserDto userDto = userServiceClient.UpdateUserDetails(user.CoreUser.UserId, user.CoreUser.FirstName, user.CoreUser.LastName, gender, user.CoreUser.EmailAddress, user.CoreUser.Msisdn, "");
return Task.FromResult<UserDto>(userDto);
}
public void Dispose()
{
throw new NotImplementedException();
}
public Task AddToRoleAsync(IdentityUser user, string roleName)
{
throw new NotImplementedException();
}
public Task<IList<string>> GetRolesAsync(IdentityUser user)
{
List<UserRoleDto> roles = userServiceClient.GetUserRoles(user.Id);
return Task.FromResult<IList<string>>(roles.Select(r => r.Role.RoleName).ToList());
}
public Task<bool> IsInRoleAsync(IdentityUser user, string roleName)
{
throw new NotImplementedException();
}
public Task RemoveFromRoleAsync(IdentityUser user, string roleName)
{
throw new NotImplementedException();
}
}
That is the UserStore. Now the issue is implementing this for Identity.
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
In the class above that comes predefined with the template, there's the line:
app.CreatePerOwinContext(ApplicationDbContext.Create);
Now I don not have an ApplicationDbContext since this is handled in the WCF. Also, in the IdentityConfig class in the App_Start folder, there's the method Create that has this line,
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
Again, i have no idea with what to replace the ApplicationDbContext. Am I doing this right? Is the tutorial I followed sufficient to help me with what I need?
I used this link, ASP.NET Identity 2.0 Extending Identity Models and Using Integer Keys Instead of Strings
The issue was more about the fact that my user id was an long instead of the default string. I also did not need to pass the context as my UserStore did not expect a context in it's constructor

Store single instance in DbContext with ASP.NET MVC 5 CF EF (no list)

I have a class called AppSettings where I store some settings of my application. So far, I only used Lists in my DbContext like
public class MyDbContext: DbContext {
public DbSet<User> Users { get; get; }
}
But for the settings, I need no list. I only want to store a single instance of my AppSettings class. I tried to set it as a normal member
public class AppSettingsContext: DbContext {
public AppSettings AppSetting { get; get; }
}
But this is not working: EF will throw an exception that the entity type AppSettings is not a part of the model for the current context. The Code:
using(var db = new AppSettingsContext()) {
var setting = new AppSettings() {
AttributeA = "Test",
//...
};
db.Entry(setting).State = EntityState.Added;
db.SaveChanges();
}
Is it possible to do this with EF? Or am I forced to implement this logic on my own by using a not mapped attribute where I make sure that only one single instance is stored and returned by the database?
If you want to store your settings in the DB, you can't store singular, that's not how TSQL works.
If you only want singular settings for a user, I would recomend web.config. If you REALLY want to store it in the DB though and want it to have a more concrete feeling you could just extend your database context like so:
public class MyDbContext: DbContext {
public DbSet<User> Users { get; get; }
public DbSet<AppSettings> AppSettings { get; set;}
}
public static class MyDbExtensions
{
public static async Task<AppSettings> DbSettings(this MyDbContext context, Guid settingsGuid)
{
return await context.AppSettings.FirstAsync(as => as.Id == settingsGuid)
}
// OR
public static async Task<AppSettings> UserSettings(this MyDbContext context)
{
return await context.AppSettings.FirstAsync(as => as.Id == UserSettingsDbGuid)
}
public static Guid UserSettingsDbGuid = "Guid of user settings goes here"
}
// example usage:
var context = GETDBCONTEXTMETHOD();
var userSettings == context.DbSettings(MyDbExtensions.UserSettingsDbGuid);
// OR
userSettings == context.UserSettings();

Are there any implementations of ASP.NET Identitity that have another level above account?

I am using ASP.NET Identity. It works well but I would like to add in a parent to the AspNetUsers table. In my case I would like to have each user belong to an organization. At this point I am just looking for some ideas to see if others have seen implementations that would allow this.
Has anyone seen any implementations that do this. I would like to get some tips on how I could implement this functionality.
I'm presuming you are using default EF implementation of Identity storage.
Identity is very flexible and can be bent into many shapes to suit your needs.
If you are looking for a simple parent-child relationship, where every user would have a parent record (such as Company), one of the ways to implement that is to add company reference to user class:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNet.Identity.EntityFramework;
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
}
[ForeignKey("CompanyId")]
public Company Company { get; set; }
public int CompanyId { get; set; }
}
public class Company
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CompanyId { get; set; }
public String Name { get; set; }
public virtual ICollection<ApplicationUser> Users { get; set; }
}
This will put a foreign key on users to companies. But from here next course of action depends on what is required in your application. I would imagine that you'll have some sort of restriction for users depending on what company they belong to. For quick company retrieval you can store CompanyId in a claim when logging in the user.
Default implementation of ApplicationUser has GenerateUserIdentityAsync method. You can modify this as following:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
identity.AddClaim(new Claim("CompanyId", CompanyId.ToString()));
return userIdentity;
}
Then on every request you'll be able to access this CompanyId claim from the cookie:
public static int GetCompanyId(this IPrincipal principal)
{
var claimsPrincipal = principal as ClaimsPrincipal;
//TODO check if claims principal is not null
var companyIdString = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "CompanyId");
//TODO check if the string is not null
var companyId = int.Parse(companyIdString); //TODO this possibly can explode. Do some validation
return companyId;
}
And then you'll be able to call this extension method from almost anywhere of your web application: HttpContext.Current.User.GetCompanyId()

Resources