0

I have a survey's platform with all the score of a list of stores.

I need to make available to each user, the stores based in his location's area (region).

I use Microsoft Identity in my MVC project and I found only two aproaches for this:

1- Build a profile for each region, so I can filter based on the logged user's profile name - dbContext.Where(location == profileName)

2- Extend the ApplicationUser to include the region, so I can filter using dbContext.Where(location == User.Identity.GetLocation())

Or any other option?

I have been able to implement the second option:

Stores Model:

public enum PTLocationDistrict
{
    [Display(Name = "Aveiro")]
    Aveiro = 1,
    [Display(Name = "Beja")]
    Beja = 2,
    [Display(Name = "Braga")]
    Braga = 3
}

[Table("Stores")]
public class Store
{
    [Key]
    public int Id { get; set; }
    public DateTime DateCreated { get; set; }
    public DateTime DateModified { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public PTLocationDistrict District { get; set; }
}

Identity ApplicationUser:

public class ApplicationUser : IdentityUser
{
    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("PTLocationDistrict", this.PTLocationDistrict.ToString()));

        return userIdentity;
    }

    // Additional properties for the Application User
    public PTLocationDistrict? PTLocationDistrict { get; set; }
}

Identity Services:

   public static PTLocationDistrict? GetPTLocationDistrict(this IIdentity identity)
    {
        Claim claim = ((ClaimsIdentity)identity).FindFirst("PTLocationDistrict");

        PTLocationDistrict value;

        if (claim == null)
        {
            return null;
        }
        else if (Enum.TryParse(claim.Value, out value))
        {
            return value;
        }
        else
        {

            return null;
        }
    }

Dashboard Report Count Service:

public int GetCountToday(PTLocationDistrict? ptLocationDistrict)
{
    try
    {
        var today = DateTime.Today;

        IQueryable<SurveySession> surveySessions = _dbContext.SurveySessions.AsNoTracking().AsQueryable();

        if (ptLocationDistrict != null)
        {
            surveySessions = surveySessions.Where(ss => ss.Location.District == ptLocationDistrict.Value);
        }

        int count = surveySessions
                            .Where(p => p.DateCreated >= today)
                            .Count();

        return count;
    }
    catch (Exception error)
    {
        _logger.Error("[Error in SurveyService.GetCountToday" - Error: " + error + "]");

        return 0;
    }
}

Controller Action:

public virtual ActionResult Index()
{
    IndexViewModel model = new IndexViewModel();
    PTLocationDistrict? ptLocationDistrict = User.Identity.GetPTLocationDistrict();

    // Realizados Hoje
    model.SurveyCountToday = _surveyService.GetCountToday(ptLocationDistrict);

}
Patrick
  • 2,995
  • 14
  • 64
  • 125
  • I'm afraid this question is a bit broad. For one thing, how are you determining location? Georouting? – nurdyguy Jun 02 '17 at 18:57
  • Hi, thanks! The location of the user is specified in the register process, based in the list of the country's regions (in this case Portugal) that will filter the stores list – Patrick Jun 02 '17 at 19:38
  • 1
    As a sidenote: I would not implement PTLocationDistrict as nullable. Add *None = 0* instead and set this as default. –  Jun 05 '17 at 13:58
  • Why, do you think is a bad practice? So I have to change the filters to "ptLocationDistrict != PTLocationDistrict.None" correct? – Patrick Jun 05 '17 at 14:00
  • It's just something I'm used to, never thought about it again. But it seems that it doesn't really matter: https://stackoverflow.com/questions/1795657/c-sharp-enums-nullable-or-unknown-value –  Jun 05 '17 at 14:31
  • 1
    After using your suggestion it seems that the code gets more clean with none then nullable values that we need to create special convertions from it. This "return PTLocationDistrict.None;" I read better then "return null" In addition, I checked the question you shared but just read the @Maarten jansonius solution, it makes sense to use None, because we are sure that a value has been defined instead of null. – Patrick Jun 05 '17 at 15:21

1 Answers1

1

It seems that the information is not used for Identity (denying access based on location). So in case you have a 'Person' table, you can store the location there, outside the Identity Model, and use this table in a join on the location field.

If not, I would simply add a claim with the location. The advantage is that you can read the claim without having to lookup the field from the database. You can use User.Identity.GetLocation(), assuming this is an extension that reads the claim containing the Location.

You can extend the IdentityUser with a field Location and add a claim, based on the field. But you don't have to if you add a claim to the AspNetUserClaims table, since it is not likeley to change. In that case the claim is automatically added to the identity.

Please note that the value of a claim is updated only if the user logs out and logs in again, or the token is expired.

-- update --

You can create a CustomClaimType, something like this:

public const string LocationClaimType = "https://schemas.mycompany.com/claims/location";

It doesn't really matter, as long as it can uniquely identify the claim.

There isn't much to tell about the AspNetUserClaims table. Insert a record where claimtype is your custom claimtype and set the value for the specific user. And it is automatically added to the identity. You can also add claims to a user with code.

In your controller:

var identity = (System.Security.Claims.ClaimsIdentity)User.Identity;
var res = identity.Claims.FirstOrDefault(c => c.ValueType == LocationClaimType)?.Value;

You can extend Identity (with GetLocation) with code like above. You can do the same thing for CompanyId.

In both cases the properties aren't likely to change and you won't need to call the database to get this additional information.

  • Hi Thanks! I don't find much information regarding the use of AspNetUserClaims table. I have create a User.Identity.GetCompanyId() in my project so I can filter each user's data, and I am very familiar with, but I don't know if it's the best of the 2 options you present. – Patrick Jun 05 '17 at 08:49
  • What does GetCompanyId do? Does it call the database or does it filter claims? And do you have a table like 'Person' in your 'business' context? –  Jun 05 '17 at 12:24
  • GetCompanyId gets the company id of the logged user, from the users table. It does not filter, it's the id stored in the identity table because I have no person table, only users. I found no good example to try this approach with the claims table. I don't how to create and manage everything until I apply the filter in the results. – Patrick Jun 05 '17 at 12:28
  • Can you help to understand the logic of your code, where and in what moment the user gets his claims defined, before or after the login? In hat moment I create the user's claim? I having trouble to put your code in mine... – Patrick Jun 05 '17 at 13:10
  • I finally get it up and running, shoud I share the code so we can discuss your solution applied to it? I think your solution is better for a long term application – Patrick Jun 05 '17 at 13:32
  • If you have additional questions or want to discuss it then I suggest you update your question with the code. –  Jun 05 '17 at 13:37
  • I have edited the question. I was able to do this version because I already had a similar filter with companyId. What do you think of it compared with claims approach using the AspNetUserClaims table? – Patrick Jun 05 '17 at 13:52
  • This is just fine, like I mentioned in the answer: you can also use code to add claims. –  Jun 05 '17 at 13:57
  • Maybe Claims are better for permissions in the application and in this case, for filter purpose, it works just fine – Patrick Jun 05 '17 at 14:00