I am using ninject in my mvc application with AspNet Identity
In my global asax I have the following line of code
kernel.Bind<IIdentityManager>().To<IdentityManager>().InRequestScope().WithConstructorArgument("conn", idConnString);
Whenever a new request is made to the controller this therefore should create a new instance of the above class.
However this is not happening. Break points show that the constructor for the above class is only called once when the website loads and is never being hit again when I refresh the page or make a new request (it is injected into a class that is called on every page request to check the claims and thus authorise or deny the user access to the page they requested). Because of this the db connection context which underpins the identitymanager remains the same and never reloads on request. Thus it never picks up any changes to the claims that have happened externally to the website which is what would happen if a new instance of IdentityManager was created on each request as I would expect it to given the above binding.
Any advice would be greatly appreciated.
EDIT to add more code. (note I've removed code not relating to this particular problem to make it shorter)
My Global.asax
protected override IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Load(Assembly.GetExecutingAssembly());
kernel.Bind<IVariables>().ToConstructor<Variables>(context => new Variables(AppSettingKeys.NRDAWebServer)).InRequestScope();
var variables = kernel.Get<IVariables>();
var masterPassword = VBHelperCore.Encryptor.EncDec.Decrypt(File.ReadAllBytes(AppSettingKeys.ConfigSettingsKeys.ConfigLocation + AppSettingKeys.SQLConnection.NRDAMasterUserName),
AppSettingKeys.HexString);
var s3ConnString = #"Data Source=" + variables.Get(AppSettingKeys.SQLConnection.NRDAMasterSQLServer, null) + #";Initial Catalog=" + variables.Get(AppSettingKeys.SQLConnection.NRDAMasterDB, null) +
";User ID=" + variables.Get(AppSettingKeys.SQLConnection.NRDAMasterUserName, null) + ";Password=" +
masterPassword +
";Trusted_Connection=False;MultipleActiveResultSets=True;";
var idConnString = s3ConnString;
kernel.Bind<IAuthenticationFilter>().To<NrdaAuthenticationAttribute>().InRequestScope();
kernel.Bind<IIdentityManager>().To<IdentityManager>().InRequestScope().WithConstructorArgument("conn", idConnString);
kernel.Bind<INrdaClaimsTransformer>().To<NrdaClaimsTransformer>().InRequestScope();
GlobalConfiguration.Configuration.DependencyResolver = new Ninject.WebApi.DependencyResolver.NinjectDependencyResolver(kernel);
return kernel;
}
This essentially loads configuration variables to get the connection string with passwords and then binds a new instance of IdentityManager with the connection string passed to its constructor.
I have defined an ActionFilter
public class NrdaAuthenticationAttribute : ActionFilterAttribute, IAuthenticationFilter
{
[Inject]
public INrdaClaimsTransformer ClaimsTransformer{ private get; set; }
[Inject]
public ILogger Log { get; set; }
public void OnAuthentication(AuthenticationContext filterContext)
{
try
{
var user = filterContext.HttpContext.User;
if ((user != null && user.Identity.IsAuthenticated))
{
ClaimsPrincipal currentPrincipal = ClaimsPrincipal.Current;
ClaimsPrincipal tranformedClaimsPrincipal = ClaimsTransformer.Authenticate(string.Empty, currentPrincipal);
Thread.CurrentPrincipal = tranformedClaimsPrincipal;
HttpContext.Current.User = tranformedClaimsPrincipal;
}
else
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
catch (SecurityException ex)
{
Log.Error(ex, "Security error" + ex.Message, null);
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
}
}
All the controllers then have so this filter is called everytime you access any controller (so on every request)
[NrdaAuthentication]
The filter uses the kernel bound Claims Transformer
public class NrdaClaimsTransformer : ClaimsAuthenticationManager, INrdaClaimsTransformer
{
[Inject]
public IIdentityManager IdentityManager { private get; set; }
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
var ret = !incomingPrincipal.Identity.IsAuthenticated
? base.Authenticate(resourceName, incomingPrincipal)
: DressUpPrincipal(incomingPrincipal);
return base.Authenticate(resourceName, ret);
}
private ClaimsPrincipal DressUpPrincipal(ClaimsPrincipal incomingPrincipal)
{
var user = IdentityManager.GetByUsername(incomingPrincipal.Identity.Name);
return null //code removed to simplify example
}
}
Obviously I've cut a lot of code out to simplify but due to the ninject bindings all being set to InRequestScope() I would expect the constructor of IdentityManager to be called on each new request. It however isn't. A breakpoint shows it's only called when the website first starts and never again after hence it must be using the same instance with every web request which it shouldn't
EDIT 2
Ok I think I've found the problem. It seems because that InRequestScope doesn't work within a custom authorisation attribute. Or indeed any of the other scope functions I've tried. I assume this is because the attribute instance is loaded into memory for the user's session and thus the injected class never expires so it doesn't need to be reloaded by ninject. When I do a test by calling the injected class using a controller and in the action it works fine. For now I will just need to load a new instance of the identitymanager each time in the attribute instead of the ninject version
Related
I have an action method that uses my authentication filter:
public class TutorAuthenticationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.HttpContext.Request;
var auth = req.Headers["Authorization"];
if (!string.IsNullOrEmpty(auth))
{
var cred = System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
var user = new { Name = cred[0], Password = cred[1] };
if (userService.AuthorizeTutor(user.Name, user.Password))
{
return;
}
}
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", $"Basic realm= {BasicRealm}");
filterContext.Result = new HttpUnauthorizedResult();
}
}
I would like to then display on main page something for user that have been authenticated this way, but this does not work in my View :(
#if (Request.IsAuthenticated)
{
<h1>Hello</h1>
}
I know it does not work because I don't use Identity, but is there any way that I can do this?
Thank you for answers :)
I suppose, that sending login and password in header is not secure. Better solution is one time when user is verified. And after checking, you can check all request.
For example, if you use FormsAuthentication and authCookie it's very simple:
Set auth mentod in web.config: <authentication mode="Forms" />
When login and password is valid, use FormsAuthentication.SetAuthCookie(userName, createPersistentCookie = true); This step is performed only once, when user login to application.
Then you can use property this.Request.IsAuthenticated in view or HttpContext.Current.Request.IsAuthenticated in controller (or filter).
And it works attribute [Authorize] on conntrolers or actions (public methods in conntrollers). When request is not authenticated, request is redirected to default (or set in web.config) login page.
Create a new extension method for the request object say (IsUserAuthenticated()) & in that method check if the user is valid.
Once this is done, you can use this new extension method the same way you are using Request.IsAuthenticated Property.
Below is the sample code, which you will need to tweak as per your needs. (specifically for
userservice
initialization)
public class RequestValidator
{
public bool IsValid(HttpRequest request)
{
bool isValid = false;
//TODO: Intitialize your userService here, may be using DI or a concrete object creation depending on your implementation
var auth = request.Headers["Authorization"];
if (!string.IsNullOrEmpty(auth))
{
var cred = System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
var user = new { Name = cred[0], Password = cred[1] };
isValid = userService.AuthorizeTutor(user.Name, user.Password))
}
return isValid;
}
}
Your attribute will change like this
public class TutorAuthenticationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.HttpContext.Request;
RequestValidator validator = new RequestValidator();
if(validator.IsValid(request))
{
return;
}
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", $"Basic realm= {BasicRealm}");
filterContext.Result = new HttpUnauthorizedResult();
}
}
And the extension method to be used on view will be
public static class Extensions
{
public static bool IsUserAuthenticated(this HttpRequest request)
{
RequestValidator validator = new RequestValidator();
return validator.IsValid(request);
}
}
Use it like this
#if(Request.IsUserAuthenticated())
{
<p>Hello</p>
}
If you want to pass the boolean value indicating if the user is authenticated, maybe it makes sense to just use the model object and pass it to the view.
Or maybe you should review your Form Authentication to make Request.IsAuthenticated working properly. This thread will help to start digging.
Another option would be to consider using the IAuthorizationFilter instead of the custom action filter. This thread will be a starting point.
Hope that helps!
To meet your purpose, you would need to set HttpContext.User to some valid IPrincipal.
So, if according to your criteria, the user is valid you just need to create a GenericPrinicpal and set HttpContext.User with the instance you have just created.
Something like this:
var genericIdentity=new GenericIdentity(user.Name, "CustomAuthType");
var genericPrincipal=new GenericPrincipal(genericIdentity, null);
HttpContext.User = genericPrincipal;
With GenericIdentity, the value of IsAuthenticated is dependent on the Name property, so as soon as the GenericIdentity has a Name, it is considered to be authenticated.
In this example, I'm setting the HttpContext.User and not the Thread.CurrentPrincipal so that you can get the IsAuthenticated from the Request.IsAuthenticated property.
Some extra and related information:
GenericIdentity Class
Principal and Identity Objects
Create GenericPrincipal and GenericIdentity Objects
Replacing a Principal Object
in your startup.cs file add this
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Login"),
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromMinutes(40)
});
I've been searching the web for this, and couldn't really find a solution that actually worked. Situation is as follows: I've got a WPF application, where I want to present the user with a simple logon form. Trying to work MVVM, so I've got a LoginViewModel with the following code behind the login command:
try
{
WithClient(servfact.GetServiceClient<IAccountService>(), proxy =>
{
principal = proxy.AuthenticateUser(Login, password);
});
Thread.CurrentPrincipal = principal;
}
catch(...) { ... }
"WithClient" is a short method in my viewmodel baseclass, which I use to instantiate and dispose of my service proxies:
protected void WithClient<T>(T proxy, Action<T> codeToExecute)
{
try { codeToExecute(proxy); }
finally
{
IDisposable toDispose = (proxy as IDisposable);
if(toDispose != null) { toDispose.Dispose(); }
}
}
Now, most of my services are Async, and I've got an async variant of WithClient going on, which also works fine:
protected async Task WithClientAsync<T>(T proxy, Func<T, Task> codeToExecute)
{
try { await codeToExecute(proxy); }
finally
{
IDisposable toDispose = (proxy as IDisposable);
if(toDispose != null) { toDispose.Dispose(); }
}
}
The trouble begins whenever I also want to do the login asynchronously. Obviously I don't want the UI to freeze up as I do the login (or visit any WCF service for that matter). That in itself is working fine, but the problem sits in the piece of code where I set the CurrentPrincipal. This problem is probably familiar to most of you: it seems to set it just fine. Then in my program I want to use the CurrentPrincipal (either on the client side or to send the users login to a WCF service in a messageheader), but it seems to be reset to a standard GenericPrincipal. When I revert the login back to being synchronous, the CurrentPrincipal is just fine. So in short: how do I set the principal in the asynchronous code, having it persist later on, instead of reverting back to a standard principal?
Well, well, no answer in a year. No worries, since I managed to solve this myself: I simply wrapped a singleton around it all:
public sealed class CurrentPrincipalFacade : IPrincipal
{
#region Singleton mechanism
private static readonly CurrentPrincipalFacade instance = new CurrentPrincipalFacade();
public static CurrentPrincipalFacade Instance { get { return instance; } }
private CurrentPrincipalFacade() { }
#endregion
#region IPrincipal members
public IPrincipal Principal { get; set; }
public IIdentity Identity { get { return Principal == null ? null : Principal.Identity; } }
public bool IsInRole(string role) { return Principal != null && Principal.IsInRole(role); }
public void Reset() { Principal = new GenericPrincipal(new GenericIdentity(""), new string[] { }); }
#endregion}
So I set that after login. I guess the problem was I was setting the principal in another thread, which got lost when I got out of that?
I have a service call DataManager that needs a reference to the current IPrincipal user. However, I don't want to use a global reference like System.Threading.Thread.CurrentPrincipal or HttpContext.Current.User because I want to be able to unit test it and specify any user for any given test. But I also don't want to construct a new DataManager for each thread in a multi-threaded environment (like WebApi or Web Services).
Is it bad practice or will it break anything if I inject (via constructor) an IPrincipal implementation that internally uses HttpContext.Current.User or HttpContext.Current.User? Otherwise how should I handle this?
The solution I am thinking of looks like this:
public class DataManager {
public DataManager(IPrincipal currentUser){
this.currentUser = currentUser;
}
private IPrincipal currentUser;
...
public void DoWork(){
var currentUserName = currentUser.Identity.Name;
// Do work here
}
}
And then, when using this in ASP.NET, pass in an IPrincipal implementation that references the HTTP context's current user:
public class CurrentUser : IPrincipal {
public IIdentity Identity {
get {
return HttpContext.Current.User.Identity;
}
}
public bool IsInRole(string role) {
return HttpContext.Current.User.IsInRole(role);
}
}
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.
I come to the conclusion I need to ditch the ASP.NET Membership (for list of reasons).
Now really the only thing I see that I need is creating a cookie(done by Form Authentication), custom methods for authentication (done) and finally validation based on if they are logged in or by role.
I am stuck on the last one.
I am trying to override the Authorize (attribute) but I have no clue how to do this. I looked at many examples and each one seems to be done differently then the next. I don't know why they do this or which one I should be using.
Some tutorials seem to do the authentication in the AuthorizeCore, Some do it in the OnAuthentication.
Some use some AuthorizationContext thing and then call this base class.
base.OnAuthorization(filterContext);
Some seem to do caching in it.
What I want is all the functionality the built in ones have but just hooked up to my custom tables. Like I going to have my own Role table. I need to tell it where that is and pull the stuff in.
Also I have no clue how to do this or how decorate the tag like this
[Authorize(Roles="test")]
References:-
http://darioquintana.com.ar/blogging/tag/aspnet-mvc/
asp.net mvc Adding to the AUTHORIZE attribute
http://davidhayden.com/blog/dave/archive/2009/04/09/CustomAuthorizationASPNETMVCFrameworkAuthorizeAttribute.aspx
Edit
This is what I have now.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class AuthorizeAttributeCustom : AuthorizeAttribute
{
public string Roles { get; set; }
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
return;
}
DataClasses1DataContext test = new DataClasses1DataContext();
var name = filterContext.HttpContext.User.Identity.Name;
var user = test.User2s.Where(u => u.userName == name).FirstOrDefault();
var role = test.Roles.Where(u => u.UserId == user.userId).Select(u => u.Role1).FirstOrDefault();
string[] split = Roles.Split(',');
if (split.Contains(role) == true)
{
// is authenticated and is in the required role
SetCachePolicy(filterContext);
return;
}
filterContext.Result = new HttpUnauthorizedResult();
}
private void SetCachePolicy(AuthorizationContext filterContext)
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
}
Out Standing Questions
Why is it sealed? If it is sealed
does it not make it harder to unit
test?
What is filterContext?
Why is no AuthorizeCore used? Only
OnAuthentication?
Whats the cache refering to? Like
is it caching the role? Or the Page?
I can't tell with the debugger it
seems to run the code every single
time.
Is caching it safe?
In general is this safe(ie no holes
in it to be explioted- kinda worried
I will screw something up and have
some major hole in my site).
Here's a custom attribute that would work just as you want it; using an Enum for role types and using cookie creation yourself, which allows for storing of roles.
usage
[AuthorizeAttributeCustom(RoleRequired = GoodRoles.YourRoleTypeHere)]
attribute code:
//http://stackoverflow.com/questions/977071/redirecting-unauthorized-controller-in-asp-net-mvc/977112#977112
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class AuthorizeAttributeCustom : AuthorizeAttribute
{
/// <summary>
/// The name of the view to render on authorization failure. Default is "Error".
/// </summary>
public string ViewName { get; set; }
public ViewDataDictionary ViewDataDictionary { get; set; }
public DeniedAccessView DeniedAccessView { get; set; }
private GoodRoles roleRequired = GoodRoles.None;
public GoodRoles RoleRequired { get{ return roleRequired;} set{ roleRequired = value;} } // this may evolve into sets and intersections with an array but KISS
public AuthorizeAttributeCustom()
{
ViewName = "DeniedAccess";
DeniedAccessView = new DeniedAccessView
{
FriendlyName = "n/a",
Message = "You do not have sufficient privileges for this operation."
};
ViewDataDictionary = new ViewDataDictionary(DeniedAccessView);
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
return;
}
if (RoleRequired == GoodRoles.None || filterContext.HttpContext.User.IsInRole(RoleRequired.ToString()))
{
// is authenticated and is in the required role
SetCachePolicy(filterContext);
return;
}
filterContext.Result = new ViewResult { ViewName = ViewName, ViewData = ViewDataDictionary };
}
private void SetCachePolicy(AuthorizationContext filterContext)
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
}
you'll need to have explicitly added your roles to auth cookie and read them back in a base controller say. my implementation has other details which you might not want so maybe best to read here: http://ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html