How to authorize a user to only see his own records with asp.net Identity 2.0 - c#

There must be an easy solution for such a generic question, so I apologize upfront for my ignorance:
I have a multi-user Web-app (Asp.net MVC5 with EF6) that a.o. allows users to view and/or modify their relevant data stored in several related tables (Company, Csearch, Candidate). (for more details see below). They should NOT see any other data (e.g. by tampering with the URL).
I use Asp.net Identity 2.0 for authentication and would like to use it for the mentioned authorization as well. Userdata is stored in the standard AspNetUser Table. I use only one context for both Identity and my Business Tables.
I guess I have to either use Roles or maybe Claims to solve this, but I cannot find any guidance on how to do that. Can anyone point me in the right direction?
I have currently solved it (for the Company Model) by adding a LINQ condition to the CompanyController, but this does not appear to be a very secure and proper way of solving the problem.
public ActionResult Index(int? id, int? csearchid)
{
var companies = db.Companies
.OrderBy(i => i.CompanyName)
.Where(t => t.UserName == User.Identity.Name);
return View(companies);
My DataModel is straightforward and I had it scaffolded using Visual Studio 2017
Through EF6 Code first I have constructed a Relational Datamodel which is roughly as follows:
a COMPANY can have multiple SEARCHES (one to many).
Each Search can have multiple CANDIDATES (one to many).
A COMPANY can have multiple USERS logging in.
Users are save in the AspNetUsers table genberated by ASP.Net Identity.
My Company model looks as follows:
public class Company
{
public int CompanyID { get; set; }
// Link naar de Userid in Identity: AspNetUsers.Id
[Display(Name = "Username")]
public string UserName { get; set; }
public string CompanyName { get; set;}
public string CompanyContactName { get; set; }
[DataType(DataType.EmailAddress)]
public string CompanyEmail { get; set; }
public string CompanyPhone { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
//One to Many Navigatie links
public virtual ICollection<Csearch> Csearches { get; set; }

Once the user is identified, you can make sure the user can only access its own data. You cannot use roles for that, since that will only define the level of access. But you can use claims.
Out-of-the-box there is a seperation of concerns. Maintain this seperation. You are not meant to query the Identity tables directly. Use the userManager for that. Also never use an Identity object as ViewModel. You may expose more than you mean to. If you keep this seperation, you'll see that it is in fact much easier.
The identity context contains all data to identify the user, the business context contains all business information, including user information. You may think that this is redundant, but the login user has really nothing in common with the business user. The login emailaddress may differ from the business.user.emailaddress (what is the meaning of the emailaddress in both cases?). Also consider the possibility to have users that cannot login (anymore).
As a rule of thumb always consider if the information is part of the identity or part of the business.
When do you need the ApplicationUser? Only for the current user or when managing users. When you query users, always use the business.user. Because all the information you need should be available there.
For the current user, add claims with the information you need. The advantage of claims is that you won't have to query the database on each call to retrieve this information, like the corresponding UserId and the (display)UserName.
How to add claims
You can, without having to extend the ApplicationUser class, add a claim to the user by adding a row to the AspNetUserClaims table. Something like:
userManager.AddClaim(id, new Claim("UserId", UserId));
On login the claim will be automatically added to the ClaimsIdentity.
You can also add claims for properties that extend the ApplicationUser:
public class ApplicationUser : IdentityUser
{
public int UserId { get; set; }
public string DisplayUserName { get; set; }
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
userIdentity.AddClaim(new Claim("UserId", UserId));
userIdentity.AddClaim(new Claim("DisplayUserName", DisplayUserName));
return userIdentity;
}
}
How to read claims
In the controller you can read the claim with code like this:
var user = (System.Security.Claims.ClaimsIdentity)User.Identity;
var userId = user.FindFirstValue("UserId");
You can use userId in your queries to filter the data for the current user or even use business.users as the only entry to retrieve data. Like db.Users(u => u.Id == userId).Companies.ToList();
Please note, the code is just an example. I didn't test all of it. It is just to give you an idea. In case something isn't clear, please let me know.

It's pretty simple really. To illustrate with the example Company you provided. Note that you should use UserId to join rather than UserName since UserName can change, but UserId will always be unique.)
Instead of having UserName in your Company table, you need to change that to UserId. Then you join the AspNetUsers table with your Company table on UserId.
For example (I prefer to use the query syntax rather than the fluent syntax):
var companies = from c in db.Companies join u in db.AspNetUsers
on c.UserId equals u.UserId
orderby c.CompanyName
where u.UserName = User.Identity.Name
select c;
If you need the username as well, then include that in your select
select new { Company = c, User = u.UserName };
However, this model does not work if you want to have multiple users per company. You either need to add CompanyId to the users table (assuming a user can't be a member of more than one company) or create a many-to-many join if a user can be a member of multiple companies.
So rather than linking the user to the company, you link the company to the user. Your current model only allows one user per company.
Another thing I see wrong here is the use of DisplayName in your entity object. That seems to indicate you are using the entity in your MVC view, which you shouldn't do. You should create a separate ViewModel.
Here is how it should look like for multiple users per company:
public class Company
{
public int CompanyID { get; set; }
// Link naar de Userid in Identity: AspNetUsers.Id
// [Display(Name = "Username")] <-- Get rid of these
// public string UserName { get; set; } <-- get rid of these
...
}
public class ApplicationUser : IdentityUser
{
public int CompanyId { get; set; }
}
Then change your query to:
var companies = from c in db.Companies join u in db.AspNetUsers
on c.CompanyId equals u.CompanyId // <-- Change to this
orderby c.CompanyName
where u.UserName = User.Identity.Name
select c;

I made it in the following way:
I added UserId property to the Company class. (It is string type because at SQL it is NVARCHAR type)
public class Company
{
public string UserId { get; set; }
public int CompanyID { get; set; }
// Link naar de Userid in Identity: AspNetUsers.Id
[Display(Name = "Username")]
public string UserName { get; set; }
public string CompanyName { get; set;}
public string CompanyContactName { get; set; }
[DataType(DataType.EmailAddress)]
public string CompanyEmail { get; set; }
public string CompanyPhone { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
//One to Many Navigatie links
public virtual ICollection<Csearch> Csearches { get; set; }
}
In the Create controller for getting current logged in user id I used How to get the current logged in user ID in ASP.NET Core? post. In brief UserId = User.FindFirstValue(ClaimTypes.NameIdentifier)
public class CompanyController : Controller
{
private readonly ApplicationDbContext _context;
private readonly IWebHostEnvironment webHostEnvironment;
public CompanyController (ApplicationDbContext context, IWebHostEnvironment hostEnvironment)
{
_context = context;
webHostEnvironment = hostEnvironment;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(RecordViewModel model)
{
if (ModelState.IsValid)
{
Company company = new Company
{
UserId = User.FindFirstValue(ClaimTypes.NameIdentifier),
FirstName = model.FirstName,
CompanyName = model.CompanyName,
CompanyContactName = model.CompanyContactName,
CompanyEmail = model.CompanyEmail,
CompanyPhone = model.CompanyPhone
};
_context.Add(company);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(model);
}
}
And for the displaying only records of the current logged in user I use following action:
public async Task<IActionResult> Index()
{
var companyLoggedInUser = from c in _context.Company
where c.UserId ==
User.FindFirstValue(ClaimTypes.NameIdentifier)
select c;
return View(companyLoggedInUser);
}

Related

EF Core. Get data from many-many relationship

I'm new to EF Core. I have many-many relationship between 2 tables. In total, I have these 3 tables:
Tenant.
UserTenant.
User.
User has property "email". I want to get all tenants that are related to user by given user email. How to do that?
I would do something like this but I think is bad approach.
var user = await dbContext.Users.FirstAsync(u => u.Email == userEmail);
var userTenants = dbContext.UserTenants.Where(u => u.UserId == user.Id);
etc...
What you could do is to create a Class ex:
public class UserEmailViewModel
{
public int Id { get; set; }
public string Email{ get; set; }
}
and Then
var userEmail = await dbContext.SqlQuery<UserEmailViewModel>("select Id, Email from User U , Tenant T , UserTenant UT where u.id=ut.Userid and t.Id=UT.TenantId ")
Note That you should add Properties to UserEmailViewModel if you want to add column to the query

Entity code first duplicate entries (MVC Identity)

Until now I have always worked with my own DAL for SQL Server.
In a new project I decided to work with Entity in a MVC project and Identity.
I use to work with bridge tables.
Here is my IdentityModels (simplified)
ApplicationUser
public class ApplicationUser : IdentityUser
{
[Required]
public string Surname { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
Group
public class Group
{
[Key]
public int Id { get; set; }
[Display(Name = "Nom du Groupe")]
[Required]
[CustomRemoteValidation("IsGroupNameExist", "Groups", AdditionalFields =
"Id")]
public string Name { get; set; }
public virtual ICollection<ApplicationUser> ApplicationUsers { get; set;
}
And DbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
public DbSet<Group> Groups { get; set; }
}
All tables I need are created and seems well created (ApplicationUser Group and ApplicationUserGroups).
The trouble is :
I have 3 groups (A, B, C) with Id 1,2,3. I"m adding a user in table ApplicationUser with 3 groups in the Groups property.
First part is OK, it adds the good values in the bridge table (ApplicationUsersGroup) BUT It adds groups A, B, C again, with Id 4,5,6 in Group table.
The CreateAsync method of UserManageris not the point (It's the same with just an Add).
If I have a look in the debugger, I can see that when I pass to the add method the user object, in the Groupsproperty, I have a ApplicationUsers property with inside the Groups property. For me, it could be the reason, but if I remove the Groups property from ApplicationUser, code first doesn't create the ApplicationUserGroups.
I'm wrong with something, but what? How can I have a user without an additional entry in Grouptable?
Thank you for your help.
UPDATE
Ok, now I understood why duplicates are added, but in my case, how to avoid that?
Here is the involved part of the Register method:
List<Group> selectedItems = new List<Group>();
foreach (GroupTableViewModel item in model.SelectedGroups)
{
if (item.Selected == true) selectedItems.Add(new Group { Id = item.Id, Name = item.GroupName });
}
var user = new ApplicationUser { Name = model.Name, Surname = model.Surname, UserName = model.Surname + "." + model.Name, Email = model.Email,Groups=selectedItems};
string password = RandomPassword.Generate(8, 8);
var result = await UserManager.CreateAsync(user, password);
CreateAsync() is the identity method. I don't understand how it adds the user (I don't see any Add() or 'SaveChanges() inside with JustDecompile).
Maybe I'm wrong again but if I want to attach an entity to the context I have to create a new context, which will be different from the context used by the CreateAsync() method.
So help needed...
This is a common issue that people unfamiliar with EF face. Because of the disconnected state of entities in the object context, EF will attempt to insert the entities in the relationships, even though they already exist. In order to solve, you need to tell EF that the entities are not new by setting their state to Unchanged. Take a look at this article from Julie Lerman and the related SO question/answer.
https://msdn.microsoft.com/en-us/magazine/dn166926.aspx
Entityframework duplicating when calling savechanges

Define a LAMBDA query in a property so it can be reused

I'm trying to defined a lambda query in a property of my code first EF model as seen below as, GetLatestTransaction :
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual List<TransactionModel> Transactions { get; set; }
public TransactionModel GetLatestTransaction {
get {
return Transactions.OrderByDescending(x => x.Created).FirstOrDefault();
}
}
}
The reason for this is that I don't want to have to retype this query in many places and by having it in one place reduce the chances of a bug.
I want to use this in a query like this:
var user = _DB.Users
.Select(u => new UserDetailsView()
{
Id = u.Id,
FirstName= u.FirstName,
LastName= u.LastName,
Balance = u.GetLatestTransaction.ValueResult
}).FirstOrDefault(x => x.Id == userId);
This is however resulting in this error:
System.NotSupportedException: 'The specified type member 'GetLatestTransaction' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.'
Is there some way to achieve this without storing another relation to the latest transaction on the user and having to update it every time there is a new transaction?
Edit: I would also like to do it as above to avoid making another query to the database, I want it all in one go to improve performance.
Your ApplicationUser class represents the table in the database. It does not represent the usage of the data in the table.
Quite a lot of people think it is good practice to separate the database structure from the usage of the data. This separation is quite often done using the repository pattern. The repository is an abstraction from the internal datastructure of the database. It allows you to add functionality to your classes without demanding this functionality in the control classes that communicate with the database.
There are numerous articles about the repository. This one helped me to understand what functionality I should put in my entity framework classes and which in the repository.
So you'll need a class that represents the elements in your database table and one that represents the applicationUsers with only their LatestTransaction
The class that represents the database table:
class ApplicationUser : IdentityUser
{
public int Id {get; set;}
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual List<TransactionModel> Transactions { get; set; }
}
ApplicationUser with the latest transaction
class AppicationUserExt : <base class needed?>
{
public int Id {get; set;}
public string FirstName { get; set; }
public string LastName { get; set; }
public TransactionModel LatestTransaction { get; set; }
}
The function to get your extended ApplicationUser is an extension function of your ApplicationUser. Input: IQueryable<ApplicationUser output: IQueryable<ApplicationUserExt>
static class MyDbContextExtensions
{
// returns ne ApplicationUserExt for every ApplicationUser
public IQueryable<ApplicationUserExt> ToExtendedUsers(this IQueryable<ApplicationUser> applicationUsers)
{
return applicationUsers
.Select(user => new ApplicationUserExt()
{
Id = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
LatestTransaction = user.Trnasactions
.OrderByDescenting(transaction => transaction.CreationDate)
.FirstOrDefault(),
}
}
}
}
So whenever you have a query with the ApplicationUsers you want, you can use ToExtendedUsers() to get the extended suers
using (var dbContext = new MyDbContext(...))
{
// you wanted to have a query like:
var result dbContext.ApplicationUsers
.Where(user => user.FirstName = "John"
&& user.LastName = "Doe");
// you'll have to add ToExtendedUsers:
var result = dbContext.ApplicationUsers
.Where(user => user.FirstName = "John"
&& user.LastName = "Doe");
.ToExtendedUsers();
}
As the result is still an IQueryable, no query has been done yet. You can still add LINQ statements before the query is done:
var result2 = result
.Where(user.LatestTransaction.Year == 2018)
.GroupBy(user => user.LatestTransaction.Date)
.OrderBy(group => group.Key)
.Take(10)
.ToList();
You see, that you can still do all kinds of LINQ stuff as long as it is an ApplicationUser. As soon as you need the LatestTransaction you convert it to an ApplicationUserExt and continue concatenating your linq statements.

Accessing additional properties of an Identity user when retrieving data from the database using EF and Linq

Lets say I added a couple of additional properties to the default User when using asp.net identity:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
I am aware that in my asp.net MVC controller I can simply do the following to get the current logged in user's name:
User.Identity.Name
So when saving to the database I can simply pass User.Identity.Name to my repository along with the object I am saving so the CreatedBy field can be populated.
Now lets say I am retrieving items from the database which have a field of CreatedBy that contains a string of the username, but I want to display Created by : FirstName + LastName in the View.
How do I obtain this extra information? If I was using pure SQL I would do an INNER JOIN on the AspNetUsers table where CreatedBy=Username and simply retrieve the FirstName and LastName in a custom column called CreatedByFullName.
Since I am using Entity Framework now along with the latest version of ASP.NET Identity I am a bit confused at how we are expected to retrieve user information to display in the View of our pages. Is it a matter of doing a join with linq in my repository or simply adding an object to each of my properties called ApplicationUser or is there better ways?
Assumptions:
You have a single tabled called ApplicationUser that contains all your users.
This table has an Id column(int) that you are reusing to store lookups in other tables.
Other classes (what I call uni-directional navigation properties):
public class BookContext : DbContext
{
public DbSet<Book> Books { get; set; }
public Dbset<ApplicationUser> Users { get; set; }
public overridee OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.HasRequired(b => b.CreatedByUser)
.WithMany()
.HasForeignKey(b => b.CreatedBy);
}
}
public class Book
{
public int CreatedBy { get; set; }
public virtual ApplicationUser CreatedByUser { get; set; }
}
Then you'd simply
using (var bookContext = new BookContext())
{
var firstBookWithRelatedUser bookContext.Books
.Include(b => b.CreatedByUser)
.First();
}
Something like that. I recommend reading the Entity Framework Documentation. Granted the above code I pretty much just wrote off the top of my head so I may not be exactly right.
If you wanted, what I call, Bi-Directional navigation properties:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Book> Books { get; set; }
}
then
public overridee OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.HasRequired(b => b.CreatedByUser)
.WithMany(u => u.Books)
.HasForeignKey(b => b.CreatedBy);
}
Then you'd simply
using (var bookContext = new BookContext())
{
var firstUserWithAllRelatedBooks = bookContext.Users
.Include(u => u.Books)
.First();
}
It really just depends on your needs. But becareful, you can end up with a Giant God DbContext that is aware of all relationships...
Sample EF query will look like below -
var result = (from tab in db.YourTable
join user in db.AspNetUsers on user.username equals tab.CreatedBy
select new {YourTableObj = tab, CreatedByFullName = user.FirstName + " " + user.LastName).ToList();

Include referenced table via LINQ under MVC3

How I can use Include of the LINQ properly under MVC3?
I created .edmx file and it has all tables.
Two of them have a relashionships
UserCategories 1..0 - * Users
I guessed to use
var userCategories = db.UserCategories.Include("Users");
in order to populate Users property. But it is always empty.
(Here there is a good example how to use it. But no success.)
How do I can fix it?
P.S. POCO class
public partial class UserCategory
{
public UserCategory()
{
this.Users = new HashSet<User>();
}
public string Name { get; set; }
public System.Guid ID { get; set; }
public virtual ICollection<User> Users { get; set; }
}
Ok, first if Users it's empty probably it's because your don't have the data in the database. Now to be specific refactor your expression like this
var userCategories = db.UserCategories.Select(x => x.Users).ToList();
This will retrieve all the users in your database which have a relation with the table UserCategory
If you just tried to obtain the users no matter the relation with the table
var users = db.Users.ToList(); // This retrieve all the users in your database
Disclaimer: note that this expressions are heave and bring all the records of your database used carefully
Class Model
{
private int userName;
.....
public UserName{get{return userName;} set{userName= value;}}
....
}
I am assuming that Model is the passing model and it has a UserName attribute. And there is a userName column in Users Table.
private dbContext db = new dbContext();
public List<Model> method(){
List<Model> m= List<Model>()
var userCategories = db.UserCategories.Include("Users");
return from item in userCategories select new Model
{
UserName = item.Users.userName
.......
}
}
This return value will be a IEnumerable<Model> it will be executed in foreach. So the

Categories