1

In my MVC application I am currently setting the Thread.CurrentPrincipal = HttpContext.Current.User in the Application_PostAuthenticateRequest() method e.g.

    protected void Application_PostAuthenticateRequest()
    {
        Thread.CurrentPrincipal = HttpContext.Current.User;
    }

This allows me to use the Thread.CurrentPrincipal in other assemblies i.e. the service layer. For example:

using System.Security;
using System.Security.Permissions;
using System.Threading;
using Microsoft.AspNet.Identity;

namespace ServiceLayer
{
public class FinancialAccount
{
    public decimal Balance { get; set; }
    public string Owner { get; set; }
}

public class FinancialAccountRepository
{
    public FinancialAccount GetById(int id)
    {
        if (id == 1)
            return new FinancialAccount {Owner = "ac40fe16-1971-4b0d-b4d5-af850d0c2c05", Balance = 40324234};

        return new FinancialAccount {Owner = "3e2d1b43-1c63-4263-8c52-44d050279596", Balance = 100};
    }
}

public class FinancialService
{
    private readonly FinancialAccountRepository _financialAccountRepository;

    public FinancialService()
    {
        _financialAccountRepository = new FinancialAccountRepository();
    }

    [PrincipalPermission(SecurityAction.Demand, Role = Constants.RoleNames.AccountHolder)]
    [PrincipalPermission(SecurityAction.Demand, Role = Constants.RoleNames.BankManager)]
    public string GetFinancialAccountDetails(int accountId)
    {
        FinancialAccount financialAccount = _financialAccountRepository.GetById(accountId);
        ThrowExceptionIfUnauthorized(financialAccount);
        return "The account balance of account: " + accountId + " is " + financialAccount.Balance.ToString("C");
    }

    private void ThrowExceptionIfUnauthorized(FinancialAccount financialAccount)
    {
        if (financialAccount.Owner != Thread.CurrentPrincipal.Identity.GetUserId() && !Thread.CurrentPrincipal.IsInRole(Constants.RoleNames.BankManager))
            throw new SecurityException();
    }
}
}

This all seems to work perfectly although I have two concerns:

  1. Is it okay to set the Thread.CurrentPrincipal in the PostAuthenticationRequest method?
  2. Is it okay to reference the using Microsoft.AspNet.Identity in my service layer?

The reason I need to reference Microsoft.AspNet.IDentity is because the IPrincipal does not contain the userId and it only contains the username.

If any of this is considered bad practice how do I get around my current issues?

Cool Breeze
  • 1,289
  • 11
  • 34

1 Answers1

2
  1. Is it okay to set the Thread.CurrentPrincipal in the PostAuthenticationRequest method?

Yes it is ok to assign Principal object (HttpContext.Current.User) to current thread.

  1. Is it okay to reference the using Microsoft.AspNet.Identity in my service layer?

It is not a good practice, although you can access it.

The reasons are -

  1. Service Layer should not be tightly couple with Presentation Layer.
  2. It is hard to unit-test the Service Layer.

Instead, you want to pass UserId as a parameter if you want UserId in service layer.

In Your Scenario

You want to return FinancialAccount instead of string value, and let presentation layer creates the text using string.Format().

The reason is you want to maintain Single Responsibility Principle. In other words, if you want to change the text later which happens very often, you do want to touch the Service Layer again.

public FinancialAccount GetFinancialAccountDetails(int accountId)
{
   return _financialAccountRepository.GetById(accountId);        
}
Win
  • 61,100
  • 13
  • 102
  • 181
  • You have basically confirmed what I was already thinking. I just don't like the idea of having to pass the user id as a parameter when I already have access to the Thread.CurrentPrincipal.Identity. Why does IIdentity have a username but not the userid? Would it be better to add the UserId as a claim during login as seen in the example here: http://stackoverflow.com/questions/22246538/access-claim-values-in-controller-in-mvc-5 – Cool Breeze Jun 04 '15 at 04:00
  • I agree in regards to returning an object instead of a string. It was just code I quickly created to help illustrate my problem. – Cool Breeze Jun 04 '15 at 04:02
  • `Why does IIdentity have a username but not the userid?` In ASP.Net Identity, you use `User.Identity.GetUserId()` to get userId inside controller. – Win Jun 04 '15 at 04:19
  • Yes but that is my whole issue. This isn't available from my service layer if I don't reference the ASP.Net Identity assembly i.e. because they are extension methods. – Cool Breeze Jun 04 '15 at 04:21
  • Is your recommended approach to just change the signature to: public FinancialAccount GetFinancialAccountDetails(int accountId, string userId)? – Cool Breeze Jun 04 '15 at 04:23
  • Again, back to the original answer, you do not want to access identity directly from service layer. Yes. `public FinancialAccount GetFinancialAccountDetails(int accountId, string userId)`. – Win Jun 04 '15 at 04:24
  • Thanks for the help. It feels a bit redundant but seems like the only viable option. The only other thing I can think of is to create another layer of abstraction i.e. create an infrastructure project that references the asp.net identity assemblies and the service layer just uses an interface from the infrastructure project? – Cool Breeze Jun 04 '15 at 04:27
  • Ideally, you want to keep Identity at Presentation Layer if possible. Then use IoC container to inject dependencies, but DI *(dependency injection)* is way out of scope of your original question. – Win Jun 04 '15 at 04:32