I am using ASP.NET MVC 4 Web application as a front-end for some WCF services. All the user log in/log out and session control is done on the back-end. MVC app should only
Here is how I did it for now:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool authorized = false;
/// MVC 4 boilerplate code follows
if (filterContext == null)
throw new ArgumentNullException("filterContext");
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);
if (skipAuthorization)
{
return;
}
if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
{
throw new InvalidOperationException(
"MyAuthorizeAttribute cannot be used within a child action caching block."
);
}
// end of MVC code
// custom code
if (!AuthorizeCore(filterContext.HttpContext))
{
// if not authorized from some other Action call, let's try extracting user data from custom encrypted cookie
var identity = MyEncryptedCookieHelper.GetFrontendIdentity(filterContext.HttpContext.Request);
// identity might be null if cookie not received
if (identity == null)
{
filterContext.HttpContext.User = new GenericPrincipal(new GenericIdentity(""), null);
}
else
{
authorized = true;
filterContext.HttpContext.User = new MyFrontendPrincipal(identity);
}
// make sure the Principal's are in sync - there might be situations when they are not!
Thread.CurrentPrincipal = filterContext.HttpContext.User;
}
// MVC 4 boilerplate code follows
if (authorized)
{
// ** 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 */);
}
else
{
HandleUnauthorizedRequest(filterContext);
}
//end of MVC code
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
// check to make sure the user is authenticated as my custom identity
var principal = httpContext.User as MyFrontendPrincipal;
if (principal == null)
return false;
var identity = principal.Identity as MyFrontendIdentity;
if (identity == null)
return false;
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// default MVC result was:
// filterContext.Result = new HttpUnauthorizedResult();
// but I redirect to index login page instead of kicking 401
filterContext.Result = new RedirectResult("/Home/Index/NeedsLogin");
}
// MVC 4 boilerplate code follows
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
// This method must be thread-safe since it is called by the caching module.
protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
bool isAuthorized = AuthorizeCore(httpContext);
return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
}
}
It does not handle my 3rd scenario, though, so I'll implement it in a global error handler.