2

I'm using ASP.NET MVC 5 and ASP.NET Identity. I have a site with multiple users, who should only see their own data. The ordinary users access the site via URLs such as "/orders", "/orders/edit/1" etc.

I also have some "admin" users, who should be able to access all the same stuff that ordinary users see, except that they can view the data for all users. What I want to do is allow them to "impersonate" a user, and see what that user sees. So, they might access the site via URLs such as "/user-foo/orders", "/user-foo/orders/edit/1", etc.

Currently, my controllers have two variants of each action: one with a user id parameter (for admin users) and one without (for ordinary users). In the latter case, the id of the logged-in user is used. Both of these then call some shared code to render the view.

However, when rendering the view, I need to ensure that any embedded links (e.g. to an order detail page) use the correct routing form (with/without user id). That means I need to constantly check whether the user is an admin, etc. Is there a better way to do this?

Gary McGill
  • 26,400
  • 25
  • 118
  • 202

2 Answers2

4

I would use the claims that are available in ASP.NET Identity. Just add a claim for the impersonated user ID that will only be used if the user is an administrator. The roles are probably already in the claims. You do not need the action that passes the ID, instead add some logic that looks at the claims to see if the person is an administrator. If they are an administrator and there is a claim containing the impersonated ID then use it instead of the logged in users ID.

Here is an article that shows how to use claims with ASP.NET Identity. This shows how to set the claims during the log-in process. If you need to add a claim after the log-in process just use the SignIn method again, like this.

var AuthenticationManager = System.Web.HttpContext.Current.GetOwinContext().Authentication;
var prinicpal = (ClaimsPrincipal)Thread.CurrentPrincipal;
prinicipal.AddClaim(new Claim(MyClaimTypes.ImpersonatedUserId, impersonatedUserId));
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = persistCookie }, principal);
Kevin Junghans
  • 17,475
  • 4
  • 45
  • 62
  • That sounds perfect, and I'm glad I asked the question. However, I can't find any info on how to actually use this stuff. All the descriptions of claims are so abstract that they're completely incomprehensible. I've yet to see any example code... But thanks, it looks like the way to go, so I'll dig in. – Gary McGill Dec 03 '13 at 21:16
  • Hmmm... Haven't been able to find any useful examples, but it struck me - is there any difference between adding a claim that says a particular user is being impersonated, and simply sticking something in the Session saying the same thing? In both cases, I can simply check for that in the action as you suggest... What benefit does using a claim have? – Gary McGill Dec 03 '13 at 22:02
  • Claims are associated with the identity of a user and is therefore a more cohesive design for what you are trying to do. Also you can use federated authentication to persist this information in a cookie or token that can be used across application domains or in a server farm environment. You may be able to achieve similar results by using the session, but I like claims for the reasons stated. Here is an article I wrote on how to obtain the roles from the claim [http://kevin-junghans.blogspot.com/2013/10/improving-performance-of.html]. – Kevin Junghans Dec 04 '13 at 13:35
  • I will have to test adding claims works with ASP.NET Identity as the security pipeline has changed significantly with the MVC 5 and OWIN. I will post my findings. – Kevin Junghans Dec 04 '13 at 13:39
  • I updated my answer to include a reference on how to use claims in ASP.Net Identity. – Kevin Junghans Dec 05 '13 at 19:42
  • Thanks. That's a big help. – Gary McGill Dec 05 '13 at 22:19
  • Hmmm... if I have an admin page that allows admin users to choose a user to impersonate, and they choose user X, then I would need to add a claim for the impersonated user ID. According to your article, the custom claim is saved in the `CurrentPrincipal` and persisted in the authentication cookie when you call `SignIn`. But in my scenario, there's no sign-in going on. The admin user is already signed-in as himself. Is there a way to modify the `CurrentPrincipal` (and cookie) without calling `SignIn`? – Gary McGill Dec 05 '13 at 22:27
  • I would just use the SignIn process again since all it is doing is setting the current principals and creating the cookie. Take a look at my updated answer. – Kevin Junghans Dec 06 '13 at 13:26
  • Following up on this for posterity now with the release of Identity 2.0.0: is this something we could now just use `UserManager.UpdateSecurityStampAsync(userId)` to cause these claims to be regenerated/reset? (Per the details at http://stackoverflow.com/a/19505060/110871 which you had directed me to in some other question you helped me with.) If I am understanding all of this correctly and that would work for this, it would be preferable to use this rather than calling `SignIn` again, yes? Thanks! – Funka May 29 '14 at 23:57
0

I normally provide the same structure for both, but with an Authorize attribute with a role specified if i am blocking access by a certain role type. Alternatively you can check the controllers User property to find out the logged on user and perform your own logic to determine what data that user has access to view.

Tim
  • 7,401
  • 13
  • 61
  • 102
  • Thanks, I too am using `[Authorize(Roles=x,y)]` to block access to the controller actions that take a user ID. And I don't have a problem accessing the data - it's the dual routing that's causing me a headache. You say you use the same structure for both, but surely you need a user ID in the URL for the admin version (since it can't be implied from the logged-on user's ID). I really don't want to have a user ID in the client-facing URL because the ASP.NET Identity user ID is a huge string of gibberish... – Gary McGill Dec 02 '13 at 21:28
  • Note to future readers of this comment using Identity 2.0.0, you can now configure Identity to use integers for the user IDs instead of GUIDs. This doesn't really answer the original question but might have made things more ... palatable ... if this were originally possible! – Funka May 30 '14 at 00:01