Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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; }
like image 931
Charles de M. Avatar asked Jan 22 '26 12:01

Charles de M.


1 Answers

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.


Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!