Anti forgery system on ASP.Net MVC - c#

When I'm putting following code:
#using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm" }))
{
#Html.AntiForgeryToken()
Log off
}
the
#Html.AntiForgeryToken()
part is thrownig following exception:
The provided identity of type 'System.Web.Security.FormsIdentity' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider setting the static property AntiForgeryConfig.AdditionalDataProvider to an instance of a type that can provide some form of unique identifier for the current user.
I've checked many examples and tried to search the web, but I cannot find any explanation. I would like to know why this error happens to me? And how to solve it to use antiforgery.

It's telling you that it won't work because despite being logged in, Membership.GetUser().UserName is not providing a name that can be used for hashing.
So your real problem is, "How come my logged in user doesn't have a username?"

There can be some cases when logged in user doesn't have Identity.Name set (in my case I have to integrate my app with some crazy log-in system). Then there are two ways around:
1) unsecure - all users will be treated the same way by antiforgery system regardless of their auth status
// System.Web.WebPages.dll
using System.Web.Helpers;
// not a production solution
public class MvcApplication : HttpApplication {
protected void Application_Start() {
AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;
}
}
2) secure - provide your own (custom) way how you distinguish your users
using System;
using System.Globalization;
using System.Web;
using System.Web.Helpers;
public class ContoscoAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider {
public string GetAdditionalData(HttpContextBase context) {
if (context == null) {
throw new ArgumentNullException("context");
}
var contoscoContext = new ContoscoHttpContext(context);
int userID = contoscoContext.GetUserID().GetValueOrDefault();
return Convert.ToString(userID, CultureInfo.InvariantCulture);
}
public bool ValidateAdditionalData(HttpContextBase context, string additionalData) {
string data = GetAdditionalData(context);
return string.Compare(data, additionalData, StringComparison.Ordinal) == 0;
}
}
public class MvcApplication : HttpApplication {
protected void Application_Start() {
AntiForgeryConfig.AdditionalDataProvider =
new ContoscoAntiForgeryAdditionalDataProvider();
}
}
where ContoscoHttpContext is class that returns UserID (or any unique user token) based on current context (i.e. HttpContextBase):
public class ContoscoHttpContext {
private HttpContextBase _context;
public ContoscoHttpContext(HttpContextBase context) {
_context = context;
}
public int? GetUserID() {
// TODO: provide your own implementation how to get user id
// based on HttpContextBase stored in _context
// in my case it was something like
// return ((ContoscoPrincipal)_context.User).UserID;
}
}

Related

Custom Authorize attribute role is not working web api authentication

I'm facing an issue while working with web api azure ad authentication
I'm having controller like below, the one which having giving proper response, But the one which having customauthorization roles throwing error as "Authentication has been for this request".
[RoutePrefix("api/hospitals")]
public class hospitals : ApiController
{
[Route("GetAll")]
[HttpGet]
[Authorize]
public async Task<IEnumerable<Hospitals>> GetAll()
{
// return ok;
}
[Route("Getbeds")]
[HttpGet]
[SmAuthorize(Constants.Roles.Admin,
Constants.Roles.HotSpitalAdmin,
Constants.Roles.QA)]
public async Task<IEnumerable<Hospitals>> Getbeds()
{
// return ok;
}
}
The Getbeds method is throwing an error as "Authorization has been request".
Please find me Custom attribute class as well
public class SmAuthorizeAttribute : AuthorizeAttribute
{
public SmAuthorizeAttribute(params string[] roles)
{
this.Roles = string.Join(",", roles.Select(s => s.Trim()).ToArray());
}
}
Can anyone help on this ?
You can refer to this SO question's answer by Derek Greer for Dot Net core, additionally I will reiterate the answer below -
The approach recommended by the ASP.Net Core team is to use the new policy design which is fully documented here. The basic idea behind the new approach is to use the new [Authorize] attribute to designate a "policy" (e.g. [Authorize( Policy = "YouNeedToBe18ToDoThis")] where the policy is registered in the application's Startup.cs to execute some block of code (i.e. ensure the user has an age claim where the age is 18 or older).
The policy design is a great addition to the framework and the ASP.Net Security Core team should be commended for its introduction. That said, it isn't well-suited for all cases. The shortcoming of this approach is that it fails to provide a convenient solution for the most common need of simply asserting that a given controller or action requires a given claim type. In the case where an application may have hundreds of discrete permissions governing CRUD operations on individual REST resources ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder", etc.), the new approach either requires repetitive one-to-one mappings between a policy name and a claim name (e.g. options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), or writing some code to perform these registrations at run time (e.g. read all claim types from a database and perform the aforementioned call in a loop). The problem with this approach for the majority of cases is that it's unnecessary overhead.
While the ASP.Net Core Security team recommends never creating your own solution, in some cases this may be the most prudent option with which to start.
The following is an implementation which uses the IAuthorizationFilter to provide a simple way to express a claim requirement for a given controller or action:
public class ClaimRequirementAttribute : TypeFilterAttribute
{
public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] {new Claim(claimType, claimValue) };
}
}
public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly Claim _claim;
public ClaimRequirementFilter(Claim claim)
{
_claim = claim;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
if (!hasClaim)
{
context.Result = new ForbidResult();
}
}
}
[Route("api/resource")]
public class MyController : Controller
{
[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
[HttpGet]
public IActionResult GetResource()
{
return Ok();
}
}
Part of this answer for .NET Framework-
Recommended Custom Attribute class:
public class CustomAuthorize : System.Web.Http.AuthorizeAttribute
{
private readonly PermissionAction[] permissionActions;
public CustomAuthorize(PermissionItem item, params PermissionAction[] permissionActions)
{
this.permissionActions = permissionActions;
}
protected override Boolean IsAuthorized(HttpActionContext actionContext)
{
var currentIdentity = actionContext.RequestContext.Principal.Identity;
if (!currentIdentity.IsAuthenticated)
return false;
var userName = currentIdentity.Name;
using (var context = new DataContext())
{
var userStore = new UserStore<AppUser>(context);
var userManager = new UserManager<AppUser>(userStore);
var user = userManager.FindByName(userName);
if (user == null)
return false;
foreach (var role in permissionActions)
if (!userManager.IsInRole(user.Id, Convert.ToString(role)))
return false;
return true;
}
}
}

How to implement Permission Based Access Control with Asp.Net Core

I am trying to implement permission based access control with aspnet core. For dynamically managing user roles and permissions(create_product, delete_product etc.), they are stored in the database. Data Model is like http://i.stack.imgur.com/CHMPE.png
Before aspnet core (in MVC 5) i was using custom AuthorizeAttribute like below to handle the issue:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _permissionName { get; set; }
[Inject]
public IAccessControlService _accessControlService { get; set; }
public CustomAuthorizeAttribute(string permissionName = "")
{
_permissionName = permissionName;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var user = _accessControlService.GetUser();
if (PermissionName != "" && !user.HasPermission(_permissionName))
{
// set error result
filterContext.HttpContext.Response.StatusCode = 403;
return;
}
filterContext.HttpContext.Items["CUSTOM_USER"] = user;
}
}
Then i was using it in action method like below:
[HttpGet]
[CustomAuthorize(PermissionEnum.PERSON_LIST)]
public ActionResult Index(PersonListQuery query){ }
Additionally, i was using HttpContext.Items["CUSTOM_USER"] in views to show or hide html part:
#if (CurrentUser.HasPermission("<Permission Name>"))
{
}
When i decided to switch aspnet core, all my plan was failed. Because there was no virtual OnAuthorization method in the AuthorizeAttribute. I tried some ways to solve problem. Those are below:
Using new policy based authorization(i think it is not suitable for
my scenerio)
Using custom AuthorizeAttribute and AuthorizationFilter(i read this
post https://stackoverflow.com/a/35863514/5426333 but i couldn’t change it properly)
Using custom middleware(how to get AuthorizeAttribute of current
action?)
Using ActionFilter(is it correct for security purpose?)
I couldn’t decide which way is the best for my scenerio and how to implement it.
First question: Is MVC5 implementation bad practice?
Second question: Do you have any suggest to implement aspnet core?
Based on the comments, here an example on how to use the policy based authorization:
public class PermissionRequirement : IAuthorizationRequirement
{
public PermissionRequirement(PermissionEnum permission)
{
Permission = permission;
}
public PermissionEnum Permission { get; }
}
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IUserPermissionsRepository permissionRepository;
public PermissionHandler(IUserPermissionsRepository permissionRepository)
{
if(permissionRepository == null)
throw new ArgumentNullException(nameof(permissionRepository));
this.permissionRepository = permissionRepository;
}
protected override void Handle(AuthorizationContext context, PermissionRequirement requirement)
{
if(context.User == null)
{
// no user authorizedd. Alternatively call context.Fail() to ensure a failure
// as another handler for this requirement may succeed
return null;
}
bool hasPermission = permissionRepository.CheckPermissionForUser(context.User, requirement.Permission);
if (hasPermission)
{
context.Succeed(requirement);
}
}
}
And register it in your Startup class:
services.AddAuthorization(options =>
{
UserDbContext context = ...;
foreach(var permission in context.Permissions)
{
// assuming .Permission is enum
options.AddPolicy(permission.Permission.ToString(),
policy => policy.Requirements.Add(new PermissionRequirement(permission.Permission)));
}
});
// Register it as scope, because it uses Repository that probably uses dbcontext
services.AddScope<IAuthorizationHandler, PermissionHandler>();
And finally in the controller
[HttpGet]
[Authorize(Policy = PermissionEnum.PERSON_LIST.ToString())]
public ActionResult Index(PersonListQuery query)
{
...
}
The advantage of this solution is that you can also have multiple handlers for a requirement, i.e. if first one succeed the second handler can determine it's a fail and you can use it with resource based authorization with little extra effort.
The policy based approach is the preferred way to do it by the ASP.NET Core team.
From blowdart:
We don't want you writing custom authorize attributes. If you need to do that we've done something wrong. Instead you should be writing authorization requirements.
I had same requirement and i have done it as below and it works fine for me. I am using .Net Core 2.0 Webapi
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Method
, AllowMultiple = true
, Inherited = true)]
public class CheckAccessAttribute : AuthorizeAttribute, IAuthorizationFilter
{
private string[] _permission;
public CheckAccessAttribute(params string[] permission)
{
_permission = permission;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
return;
}
IRepository service =
(IRepositoryWrapper)context.HttpContext.RequestServices.GetService(typeof(IRepository));
var success = service.CheckAccess(userName, _permission.ToList());
if (!success)
{
context.Result = JsonFormatter.GetErrorJsonObject(
CommonResource.error_unauthorized,
StatusCodeEnum.Forbidden);
return;
}
return;
}
}
In Controller use it like below
[HttpPost]
[CheckAccess(Permission.CreateGroup)]
public JsonResult POST([FromBody]Group group)
{
// your code api code here.
}
For a solution that doesn't require you to add a policy for each permission see my answer for another question.
It lets you decorate your Controllers and Actions with any custom attributes you wish, and access them in your AuthorizationHandler.

How to make Singleton in MVC 5 session specific?

I have a Singleton model class in my MVC application to determine if the user logging in has authorization/admin (based on memberships to certain AD groups). This model class needs to be a Singleton so that the user's access rights can be established once at first logon and used throughout the session:
public sealed class ApplicationUser
{
// SINGLETON IMPLEMENTATION
// from http://csharpindepth.com/articles/general/singleton.aspx#lazy
public static ApplicationUser CurrentUser { get { return lazy.Value; } }
private static readonly Lazy<ApplicationUser> lazy =
new Lazy<ApplicationUser>(() => new ApplicationUser());
private ApplicationUser()
{
GetUserDetails(); // determine if user is authorized/admin
}
// Public members
public string Name { get { return name; } }
public bool IsAuthorized { get { return isAuthorized; } }
public bool IsAdmin { get { return isAdmin; } }
// Private members
// more code
}
The Singleton is instantiated for the first time in my EntryPointController that all other controllers derive from:
public abstract class EntryPointController : Controller
{
// this is where the ApplicationUser class in instantiated for the first time
protected ApplicationUser currentUser = ApplicationUser.CurrentUser;
// more code
// all other controllers derive from this
}
This patterns allows me to use ApplicationUser.CurrentUser.Name or ApplicationUser.CurrentUser.IsAuthorized etc all over my application.
However, the problem is this:
The Singleton holds the reference of the very first user that logs in at the launch of the web application! All subsequent users who log in see the name of the earliest logged-in user!
How can I make the Singleton session specific?
I think you are looking for the Multiton pattern, where each instance is linked to a key.
An example from here
http://designpatternsindotnet.blogspot.ie/2012/07/multiton.html
using System.Collections.Generic;
using System.Linq;
namespace DesignPatterns
{
public class Multiton
{
//read-only dictionary to track multitons
private static IDictionary<int, Multiton> _Tracker = new Dictionary<int, Multiton> { };
private Multiton()
{
}
public static Multiton GetInstance(int key)
{
//value to return
Multiton item = null;
//lock collection to prevent changes during operation
lock (_Tracker)
{
//if value not found, create and add
if(!_Tracker.TryGetValue(key, out item))
{
item = new Multiton();
//calculate next key
int newIdent = _Tracker.Keys.Max() + 1;
//add item
_Tracker.Add(newIdent, item);
}
}
return item;
}
}
}
I got it working with a mixed Singleton-Multiton approach (thanks #Kickaha for the Multiton pointer).
public sealed class ApplicationUser
{
// SINGLETON-LIKE REFERENCE TO CURRENT USER ONLY
public static ApplicationUser CurrentUser
{
get
{
return GetUser(HttpContext.Current.User.Identity.Name);
}
}
// MULTITON IMPLEMENTATION (based on http://stackoverflow.com/a/32238734/979621)
private static Dictionary<string, ApplicationUser> applicationUsers
= new Dictionary<string, ApplicationUser>();
private static ApplicationUser GetUser(string username)
{
ApplicationUser user = null;
//lock collection to prevent changes during operation
lock (applicationUsers)
{
// find existing value, or create a new one and add
if (!applicationUsers.TryGetValue(username, out user))
{
user = new ApplicationUser();
applicationUsers.Add(username, user);
}
}
return user;
}
private ApplicationUser()
{
GetUserDetails(); // determine current user's AD groups and access level
}
// REST OF THE CLASS CODE
public string Name { get { return name; } }
public bool IsAuthorized { get { return isAuthorized; } }
public bool IsAdmin { get { return isAdmin; } }
private string name = HttpContext.Current.User.Identity.Name;
private bool isAuthorized = false;
private bool isAdmin = false;
// Get User details
private void GetUserDetails()
{
// Check user's AD groups and determine isAuthorized and isAdmin
}
}
No changes to my model and controllers.
The current user's object is instantiated in the EntryPointController:
public abstract class EntryPointController : Controller
{
// this is where the ApplicationUser class in instantiated for the first time
protected ApplicationUser currentUser = ApplicationUser.CurrentUser;
// more code
// all other controllers derive from this
}
In my model and everywhere else, I can access the current user's properties using ApplicationUser.CurrentUser.Name or ApplicationUser.CurrentUser.IsAuthorized etc.
How can I make the Singleton session specific?
Will lead to your problem below.
The Singleton holds the reference of the very first user that logs in
at the launch of the web application! All subsequent users who log in
see the name of the earliest logged-in user!
I think you just simply need to store your ApplicationUser object in session per user.
The mechanism should look like this:
Create an instance of your ApplicationUser every authenticated user.
Store ApplicationUser instance in a session with key. ( Don't worry about same key per user because ASP.NET HttpSessionState will handle it for you. )
If you want to access your ApplicationUser object per user just simply get it from HttpSessionState.
You have an option to create/re-create your session in Session_OnStart or in your base controller.
Setup your session setting if you want it to expire or not.
I hope this solution will make sense to you. :)

Null User on HttpContext obtained from StructureMap

Ok, my previous question/setup had too many variables, so I'm stripping this down to it's bare bones components.
Given the code below using StructureMap3...
//IoC setup
For<HttpContextBase>().UseSpecial(x => x.ConstructedBy(y => HttpContext.Current != null ? new HttpContextWrapper(HttpContext.Current) : null ));
For<ICurrentUser>().Use<CurrentUser>();
//Classes used
public class CurrentUser : ICurrentUser
{
public CurrentUser(HttpContextBase httpContext)
{
if (httpContext == null) return;
if (httpContext.User == null) return;
var user = httpContext.User;
if (!user.Identity.IsAuthenticated) return;
UserId = httpContext.User.GetIdentityId().GetValueOrDefault();
UserName = httpContext.User.Identity.Name;
}
public Guid UserId { get; set; }
public string UserName { get; set; }
}
public static class ClaimsExtensionMethods
public static Guid? GetIdentityId(this IPrincipal principal)
{
//Account for possible nulls
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
return null;
var claimsIdentity = claimsPrincipal.Identity as ClaimsIdentity;
if (claimsIdentity == null)
return null;
var claim = claimsIdentity.FindFirst(x => x.Type == ClaimTypes.NameIdentifier);
if (claim == null)
return null;
//Account for possible invalid value since claim values are strings
Guid? id = null;
try
{
id = Guid.Parse(claim.Value);
}
catch (ArgumentNullException) { }
catch (FormatException) { }
return id;
}
}
How is this possible in the Watch window?
I have a web application that I'm upgrading to using StructureMap 3.x from 2.x, but I'm getting odd behavior on specific dependency.
I have a ISecurityService that I use to obtain verify some things when a user requests a page. This service depends on a small interface that I've called ICurrentUser. The class implementation is pretty plain, really it could be a struct.
public interface ICurrentUser
{
Guid UserId { get; }
string UserName { get; }
}
This is obtained via dependency injection using the below code.
For<ICurrentUser>().Use(ctx => getCurrentUser(ctx.GetInstance<HttpContextBase>()));
For<HttpContextBase>().Use(() => getHttpContext());
private HttpContextBase getHttpContext()
{
return new HttpContextWrapper(HttpContext.Current);
}
private ICurrentUser getCurrentUser(HttpContextBase httpContext)
{
if (httpContext == null) return null;
if (httpContext.User == null) return null; // <---
var user = httpContext.User;
if (!user.Identity.IsAuthenticated) return null;
var personId = user.GetIdentityId().GetValueOrDefault();
return new CurrentUser(personId, ClaimsPrincipal.Current.Identity.Name);
}
When a request comes in, my site wide authentication happens first, which depends on ISecurityService. This happens inside of OWIN and appears to occur before HttpContext.User has been populated, so it's null, so be it.
Later on, I have an ActionFilter that checks, via a ISecurityService, if the current user has agreed to the current version of the TermsOfUse for the site, if not they are redirected to the page to agree to them first.
This all worked fine in structuremap 2.x. For my migration to StructureMap3 I've installed the Nuget package StructureMap.MVC5 to help speed things up for me.
When my code gets to the line in my ActionFilter for checking the terms of use I have this.
var securityService = DependencyResolver.Current.GetService<ISecurityService>();
agreed = securityService.CheckLoginAgreedToTermsOfUse();
Inside of CheckLoginAgreedToTermsOfUse(), my instance of CurrentUser is null. Even though it would hazve succeeded, and my breakpoint inside of getCurrentUser() never seems to be hit. Its almost as if it's a foregone conclusion, since it was null the last time , even though it would have resolved this time.
I'm kind of baffled as to why getCurrentUser() is never called on the request for ISecurityService. I even tried explicitly sticking a .LifecycleIs<UniquePerRequestLifecycle>() on my hookup for handling ICurrentUser with no effect.
UPDATE:
Ok so just a heads up, I've started using the method accepted below, and while it has worked great so far, it didn't resolve my core problem. Turns out the new StructureMap.MVC5, based on StructureMap3, uses NestedContainers. Which scope their requests to the lifetime of the NestedContainer, regardless of the default being Transient. So when I requested HttpContextBase for the first time, it will then return that same instance for the rest of the request (even though later on in the request lifespan, the context has changed. You need to either not use NestedContainer (which, as I understand it will complicate things ASP.NET vNext), or you explicitly set the lifecycle of the For<>().Use<>() mapping to give you a new instance per request. Note that this scoping per NestedContainer causes problems with Controllers as well in MVC. While the StructureMap.MVC5 package handles this with a ControllerConvention, it does not handle Views, and recursive views or views used multiple times will likely cause you problems as well. I'm still looking for a permanent fix for the Views problem, for the moment I've reverted to the DefaultContainer.
I haven't worked with OWIN, but when hosting in IIS integrated mode the HttpContext is not populated until after the HttpApplication.Start event is complete. In terms of DI, this means that you cannot rely on using properties of HttpContext in any constructor.
This makes sense if you think about it because the application should be initialized outside of any individual user context.
To get around this, you could inject an abstract factory into your ICurrentUser implementation and to use a Singleton pattern to access it, which guarantees HttpContext won't be accessed until it is populated.
public interface IHttpContextFactory
{
HttpContextBase Create();
}
public class HttpContextFactory
: IHttpContextFactory
{
public virtual HttpContextBase Create()
{
return new HttpContextWrapper(HttpContext.Current);
}
}
public class CurrentUser // : ICurrentUser
{
public CurrentUser(IHttpContextFactory httpContextFactory)
{
// Using a guard clause ensures that if the DI container fails
// to provide the dependency you will get an exception
if (httpContextFactory == null) throw new ArgumentNullException("httpContextFactory");
this.httpContextFactory = httpContextFactory;
}
// Using a readonly variable ensures the value can only be set in the constructor
private readonly IHttpContextFactory httpContextFactory;
private HttpContextBase httpContext = null;
private Guid userId = Guid.Empty;
private string userName = null;
// Singleton pattern to access HTTP context at the right time
private HttpContextBase HttpContext
{
get
{
if (this.httpContext == null)
{
this.httpContext = this.httpContextFactory.Create();
}
return this.httpContext;
}
}
public Guid UserId
{
get
{
var user = this.HttpContext.User;
if (this.userId == Guid.Empty && user != null && user.Identity.IsAuthenticated)
{
this.userId = user.GetIdentityId().GetValueOrDefault();
}
return this.userId;
}
set { this.userId = value; }
}
public string UserName
{
get
{
var user = this.HttpContext.User;
if (this.userName == null && user != null && user.Identity.IsAuthenticated)
{
this.userName = user.Identity.Name;
}
return this.userName;
}
set { this.userName = value; }
}
}
Personally, I would make the UserId and UserName properties readonly, which would simplify the design and ensure they don't get hijacked elsewhere in the application. I would also make an IClaimsIdentityRetriever service that is injected into the constructor of ICurrentUser instead of retrieving the claims Id in an extension method. Extension methods go against the grain of DI and are generally only useful for tasks that are guaranteed not to have any dependencies (such as string or sequence manipulation). The loose coupling of making it a service also means you can easily swap or extend the implementation.
Of course, this implies that you cannot call the UserId or UserName properties of your CurrentUser class in any constructor as well. If any other class depends on ICurrentUser, you may also need an ICurrentUserFactory in order to safely use it.
Abstract factory is a lifesaver when dealing with difficult-to-inject dependencies and solves a host of problems including this one.

Is this Custom Principal in Base Controller ASP.NET MVC 3 terribly inefficient?

Despite the fact that I've been on here for a while, this is my first ever question on SO, so please be gentle with me.
I'm using ASP.NET MVC 3 and I want to create a custom Principal so I can store a bit more info about the current user than is standard thus not have to go to the database too often. It's fairly standard stuff that I'm after. Let's just say email address and user id in the first instance.
I have decided to store the object in the cache as I am aware that it is not advised to store it in the session.
I also don't want to have to keep casting the User object, so I wanted to override the User object in the controller. So I can just go User.UserId and be guaranteed of something.
So I created a custom principal like this:
public class MyPrincipal : IPrincipal
{
public MyPrincipal(IIdentity ident, List<string> roles, string email, Guid userId)
{
this._identity = ident;
this._roles = roles;
this._email = email;
this._userId = userId;
}
IIdentity _identity;
public IIdentity Identity
{
get { return _identity; }
}
private List<string> _roles;
public bool IsInRole(string role)
{
return _roles.Contains(role);
}
private string _email;
public string Email
{
get { return _email; }
}
private Guid _userId;
public Guid UserId
{
get { return _userId; }
}
}
And I have a Base Controller like this:
public class BaseController : Controller
{
protected virtual new MyPrincipal User
{
get
{
if (base.User is MyPrincipal)
{
return base.User as MyPrincipal;
}
else
{
return new MyPrincipal(base.User.Identity, new List<string>(0), "", Guid.Empty );
}
}
}
protected override void OnAuthorization(AuthorizationContext filterContext)
{
if (User != null)
{
if (User.Identity.IsAuthenticated)
{
if (User.Identity is FormsIdentity)
{
FormsIdentity id = base.User.Identity as FormsIdentity;
MyPrincipal principal = (MyPrincipal)filterContext.HttpContext.Cache.Get(id.Name);
if (principal == null)
{
MembershipUser user = Membership.GetUser();
// Create and populate your Principal object with the needed data and Roles.
principal = new MyPrincipal(id, Roles.GetRolesForUser(id.Name).ToList(), user.Email, (Guid)user.ProviderUserKey);
filterContext.HttpContext.Cache.Add(
id.Name,
principal,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
new System.TimeSpan(0, 30, 0),
System.Web.Caching.CacheItemPriority.Default,
null);
}
filterContext.HttpContext.User = principal;
System.Threading.Thread.CurrentPrincipal = principal;
base.OnAuthorization(filterContext);
}
}
}
}
}
If you have a look you will quickly realise that if the user has not logged in then any call to the User object will have to run through this bit of code:
return new MyPrincipal(base.User.Identity, new List<string>(0), "", Guid.Empty );
and this feels terribly inefficient to me, although it's only creating empty objects for the missing stuff.
It works fine.
So I guess I want to know if this is actually okay and I should stop being so anal about performance and efficiency, or if my fears are correct, in which case what should I be doing instead? [Please don't say "Getting a life, mate!"]
No - there is nothing specifically wrong with this code from a performance stand point that stands out. PLENTY of objects are creating on the back end in ASP.NET, your single object is a drop in the bucket. Since class instantiation is extremely fast I wouldn't be concerned about it.
Why are you ignoring sessions here? Session information doesn't have expiration dates, so there is no extra check behind the scenes. Unless you are using an out of proc session server, there is no serialization of your object (none with the cache either).
The cache is for every user - so you right a chance (albeit slight) of a code error returning the wrong principal where a cache being per user - does not run the risk of that.
If you want this available for all requests there (not just MVC based) I would consider setting this in Application_PostAuthenticateRequest
This post may be of use. Notice the use of userdata in the authentication ticket.
ASP.NET MVC - Set custom IIdentity or IPrincipal

Categories

Resources