MVC Removing ApplicationCookie from Response - asp.net-mvc

In previous MVC projects using forms authentication i was able to strip the authentication cookie from the response using an action filter with the following override.
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
}
I have now switched over to use OWIN based asp.net identity 2.0 and in the same way I would like to remove their version of the authentication cookie.
I have modified the filter (below) to use the new cookie name but the cookie is no longer being removed.
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
const string authenticationCookie = CookieAuthenticationDefaults.CookiePrefix + DefaultAuthenticationTypes.ApplicationCookie;
filterContext.HttpContext.Response.Cookies.Remove(authenticationCookie);
}
Does anyone know why?

The reason is that the authentication occurs in the OWIN pipeline with it's own environment dictionary, whereas your previous FormsAuthentication was using System.Web.HttpContext.
If you set a breakpoint on :
filterContext.HttpContext.Response.Cookies
and view the variable you will see it doesn't even have a cookie named .AspNet.ApplicationCookie so you're not removing anything.
I'm not sure what you're trying to achieve by removing the cookie rather than just logging the user out but a way to do something similar would be to create an action filter as below:
public class CookieStripperAttribute : ActionFilterAttribute {
public override void OnResultExecuted(ResultExecutedContext filterContext) {
filterContext.HttpContext.GetOwinContext().Environment.Add("StripAspCookie", true);
}
}
Apply that as necessary and then do a check for the action prior to OWIN writing out the message headers by creating some OWIN middleware
public class AuthenticationMiddleware : OwinMiddleware
{
const string _authenticationCookie = CookieAuthenticationDefaults.CookiePrefix + DefaultAuthenticationTypes.ApplicationCookie;
public AuthenticationMiddleware(OwinMiddleware next) :
base(next) { }
public override async Task Invoke(IOwinContext context)
{
var response = context.Response;
response.OnSendingHeaders(state =>
{
var resp = (OwinResponse)state;
if (resp.Environment.ContainsKey("StripAspCookie"))
{
resp.Cookies.Delete(_authenticationCookie);
}
}, response);
await Next.Invoke(context);
}
}
You would attach that middleware in your startup class:
app.Use(typeof(AuthenticationMiddleware));
Note though that while this will eat the cookie it wont log the user out but as I say I'm not sure if that is your intention anyway.

Related

Alternative way to handle controller action authorization instead of using customattribute

i have webapi action which is decorated with customauthattribute for authorization. This attribute internally checks with db if current user has viewcustomer permissions. Does anyone know better way of handling it instead of using customattribute. may be intercepting somewhere all request and run authorization checks for user/permisson/resource he is trying to access : eg getcustomer for customer id 10. So if user doesnt have access see customer id 10 he should get 403 status.
[CheckPermission(Process.CustomerManagment,Permissions.View)]
public IHttpActionResult GetCustomer(int customerId)
{
}
You can add global filters in the configuration of your web api.
Practically in your startup class (startup.cs or webapi.config) you can call on the httpconfiguration object the following method
var config = new HttpConfiguration();
config.Filters.Add(new MyAuthFilterAttribute());
In this way it will be global for all your api calls.
You should extend the IAuthenticationFilter interface.
take a look here for documentation
webapi documentation
One option would be to create a filter that you can apply globally. For example, something like this. Yes it's horrible but gives you a start:
public class GlobalAuthoriseAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var actionName = filterContext.ActionDescriptor.ActionName;
switch (controllerName)
{
case "Home":
//All call to home controller are allowed
return;
case "Admin":
filterContext.Result = new HttpUnauthorizedResult();
return;
}
}
}
Now you can add this to your entire app in the App_Start\FilterConfig.cs file:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new GlobalAuthoriseAttribute());
}

How to show a page only once in asp.net mvc

In my app, I am checking if some config file is available or not, if it's not then I want to redirect to install page.
To me the best place to accomplish this is application_start. Because it's happening for only one time. If I do the checking in application_start and write Response.Redirect I will get Response is not available in this context.
I tried other answers in stack overflow to redirect in application_start like HttpContext.Current.Response.Redirect; none worked for me.
I don't want to do it in a base controller or a filter because the checking logic will happen for every single request.
My goal is to check it only once and it's best to be when the app start.
Update 1
I added response.redirect to the application_start but got error like this:
application start:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Response.RedirectToRoute(
new RouteValueDictionary {
{ "Controller", "Home" },
{ "Action", "about" }
});
}
but i am receiving an error like this:
Response is not available in this context.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Web.HttpException: Response is not available in this context.
If you really want to avoid having a filter run for every request after setup then you can do something like this:
RedirectAttribute.cs (generic example)
public class RedirectAttribute : ActionFilterAttribute
{
private readonly string _controller;
private readonly string _action;
public RedirectAttribute(string controller, string action)
{
_controller = controller;
_action = action;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.ActionName != _action ||
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName != _controller)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new {controller = _controller, action = _action})
);
}
}
}
In Global.asax.cs above "FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);"
if (/*Insert logic to check if the config file does NOT exist*/)
{
//Replace "Setup" and "Index" with your setup controller and action below
GlobalFilters.Filters.Add(new RedirectAttribute("Setup", "Index"));
}
Now, after your user has fully completed setup, you can unload the app domain:
HttpRuntime.UnloadAppDomain();
Please note: you will need to make sure that your app has permission to unload the AppDomain. If it does not, you can try File.SetLastWriteTimeUtc(...) on the configuration file (AppDomain.CurrentDomain.SetupInformation.ConfigurationFile.) This will also unload the AppDomain.
Unloading the AppDomain will "restart" the web app and call Application_Start() again. The filter will not be added to your requests since your if statement will determine that the app has already been configured.
As a workaround, you could use lazy initialization in a static variable inside a filter. The actual file operations to check for the config file will only happen once during the first request. After that the value of the check for the config file is saved in the static Lazy variable. As an added bonus it's also threadsafe.
In the end the check still happens on every request, but the operation is fast after the initial check because the result of the check is saved in memory.
public class ConfigFileCheckAttribute : ActionFilterAttribute
{
//Lazy<> is threadsafe and will call the Func in the constructor just once
private static Lazy<bool> _configFileExists = new Lazy<bool>(ConfigFileExists);
private static bool ConfigFileExists()
{
//Logic to check for config file here
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!_configFileExists.Value)
{
//set your redirect here
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Setup", action = "Configure" }));
}
}
}
The last bit is to register the filter in your App_Start/FilterConfig.cs:
public static class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//...
filters.Add(new ConfigFileCheckAttribute());
}
}
If you need this feature for a specific page, use cookies like bellow inside the action:
public ActionResult Index()
{
string cookieName = "NotFirstTime";
if(this.ControllerContext.HttpContext.Request.Cookies.AllKeys.Contains(cookieName))
// not first time
return View();
else
{
// first time
// add a cookie.
HttpCookie cookie = new HttpCookie(cookieName);
cookie.Value = "anything you like: date etc.";
this.ControllerContext.HttpContext.Response.Cookies.Add(cookie);
// redirect to the page for first time visit.
return View("FirstTime");
}
}
If you want to use it for each action method, You need to write an ActionFilterAttribute to call it every time needed.
Note that the Request.Context is not longer available to the Application_Start event

Sliding Session Expiration with ServiceStack Authentication on ASP.NET MVC

When using ServiceStack authentication with ASP.NET MVC, I wanted to implement a sliding session expiration. After some help from #mythz, I got it working. For any who want to do the same, see my answer for my final implementation.
Here's my final implementation for a sliding session when using ServiceStack authentication in ASP.NET MVC...
public class SlideSessionExpirationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var ssController = filterContext.Controller as ServiceStackController;
if (ssController == null) return;
// Get session and re-save to slide/update the expiration date
var session = ssController.ServiceStackRequest.GetSession();
if(session != null && session.IsAuthenticated)
ssController.ServiceStackRequest.SaveSession(session, AppHost.SessionExpiration);
base.OnActionExecuting(filterContext);
}
}
...where AppHost.SessionExpiration is a static readonly TimeSpan that I declared in AppHost.cs. To use it, you can slap the attribute on a controller or method via [SlideSessionExpiration] or you can add it in via a global filter (like I did) inside FilterConfig.cs via...
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
/* ... other filters ... */
filters.Add(new SlideSessionExpirationAttribute());
}

asp.net MVC 3 applying AuthorizeAttribute to areas

I'm currently writing an Admin MVC 3 site, and each user only has access to certain parts of the site.
The areas of my site are the same as the user Roles, so what I would like to do is the put the AuthorizeAttribute on each area, using the area's name as the parameter in the Role.
So far I've got this to work when I'm hard coding the checking of each area, but I would like to just loop through all areas and apply the Authorize filter.
(i'm using this as my custom FilterProvider - http://www.dotnetcurry.com/ShowArticle.aspx?ID=578)
My code so far ("Gcm" is one of my areas, and is also a Role) :
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
// for all controllers, run AdminAuthorizeAttribute to make sure they're at least logged in
filters.Add(ObjectFactory.GetInstance<AdminAuthorizeAttribute>());
AdminAuthorizeAttribute gcmAuthroizeAttribute = ObjectFactory.GetInstance<AdminAuthorizeAttribute>();
gcmAuthroizeAttribute.Roles = "Gcm";
var provider = new FilterProvider();
provider.Add(
x =>
x.RouteData.DataTokens["area"] != null && x.RouteData.DataTokens["area"].ToString() == "Gcm"
? gcmAuthroizeAttribute
: null);
FilterProviders.Providers.Add(provider);
}
Does anyone know how to get all the areas of my application, so I can just loop through them, rather than hard coding each area?
Or if anyone has a better idea of how to Authorize per area, that would be appreciated to.
Thanks for your help
Saan
You could you make a base controller for each area, and put the authorize attribute over the base class. That way you can pass the area parameter in for each area's base controller.
Here is an example of a Authorize Attribute override i have created. I needed my authorize function to support to types of member ship so you might not want to get too into the inner workings of the functions, but AuthorizeCore is where the main logic occures. In my case i am checking it against a entity datacontext.
Usage:
[AjaxAuthorize(AjaxRole = "Administrators")]
public JsonResult SaveAdministrativeUser(v.... )
Code:
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
private class HttpAuthorizeFailedResult : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
// Set the response code to 403. Membership.Provider.Name == "UnitArchiveMembershipProvider"
context.HttpContext.Response.StatusCode = context.HttpContext. User.Identity is WindowsIdentity ? 401 : 403;
}
}
public string AjaxRole { get; set;}
public AjaxAuthorizeAttribute()
{
AjaxRole = "Users";
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (string.IsNullOrEmpty(MvcApplication.Config.DBSettings.Database))
{
return true;
}
//When authorize parameter is set to false, not authorization should be performed.
UnitArchiveData db = DataContextFactory.GetWebRequestScopedDataContext<UnitArchiveData>(MvcApplication.Config.DBSettings.GetConnectionString());
if (httpContext.User.Identity.IsAuthenticated)
{
login_data user = db.login_datas.Where(n => n.EmailAddress == httpContext.User.Identity.Name).FirstOrDefault();
if (user != null)
{
return user.cd_login_role.RoleName == "Administrators" || user.cd_login_role.RoleName == AjaxRole;
}
}
return false;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
//Ajax request doesn't return to login page, it just returns 403 error.
filterContext.Result = new HttpAuthorizeFailedResult();
}
else
base.HandleUnauthorizedRequest(filterContext);
}
}
When I was investigating a separate issue, I came across How to pass parameters to a custom ActionFilter in ASP.NET MVC 2?
That attribute example can be altered to check for the current Controller's area.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
RouteData routeData = filterContext.RouteData;
// check if user is allowed on this page
if (SessionFactory.GetSession().Contains(SessionKey.User))
{
User user = (User)SessionFactory.GetSession().Get(SessionKey.User);
string thisArea = routeData.DataTokens["area"].ToString();
// if the user doesn't have access to this area
if (!user.IsInRole(thisArea))
{
HandleUnauthorizedRequest(filterContext);
}
}
// do normal OnAuthorization checks too
base.OnAuthorization(filterContext);
}
}
I then apply my custom authorize attribute to all controllers like this in Global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
// for all controllers, run CustomAuthorizeAttribute to make sure they're at logged in and have access to area
filters.Add(ObjectFactory.GetInstance<CustomAuthorizeAttribute>());
}
Thanks for all who replied
Saan

ASP.NET MVC: How to automatically disable [RequireHttps] on localhost?

I want my login page to be SSL only:
[RequireHttps]
public ActionResult Login()
{
if (Helper.LoggedIn)
{
Response.Redirect("/account/stats");
}
return View();
}
But obviously it doesn't work on localhost when I develop and debug my application. I don't wanna use IIS 7 with SSL certificates, how can I automatically disable the RequireHttps attribute?
Update
Based on info provided by StackOverflow users and ASP.NET MVC 2 source code I created the following class that solves the problem.
public class RequireSSLAttribute : FilterAttribute, IAuthorizationFilter
{
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.HttpContext.Request.IsSecureConnection)
{
HandleNonHttpsRequest(filterContext);
}
}
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.Url.Host.Contains("localhost")) return;
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The requested resource can only be accessed via SSL");
}
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
And it's used like this:
[RequireSSL]
public ActionResult Login()
{
if (Helper.LoggedIn)
{
Response.Redirect("/account/stats");
}
return View();
}
The easiest thing would be to derive a new attribute from RequireHttps and override HandleNonHttpsRequest
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.Request.Url.Host.Contains("localhost"))
{
base.HandleNonHttpsRequest(filterContext);
}
}
HandleNonHttpsRequest is the method that throws the exception, here all we're doing is not calling it if the host is localhost (and as Jeff says in his comment you could extend this to test environments or in fact any other exceptions you want).
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
if (!HttpContext.Current.IsDebuggingEnabled) {
filters.Add(new RequireHttpsAttribute());
}
}
#if (!DEBUG)
[RequireHttps]
#endif
public ActionResult Login()
{
if (Helper.LoggedIn)
{
Response.Redirect("/account/stats");
}
return View();
}
You can encapsulate this requirement in a derived attribute:
class RequireHttpsNonDebugAttribute : RequireHttpsAttribute {
public override void HandleNonHttpsRequest(AuthorizationContext ctx) {
#if (!DEBUG)
base.HandleNonHttpsRequest(ctx);
#endif
}
}
MVC 6 (ASP.NET Core 1.0):
The proper solution would be to use env.IsProduction() or env.IsDevelopment().
Example:
Startup.cs - AddMvc with a custom filter:
public void ConfigureServices(IServiceCollection services)
{
// TODO: Register other services
services.AddMvc(options =>
{
options.Filters.Add(typeof(RequireHttpsInProductionAttribute));
});
}
Custom filter inherit from RequireHttpsAttribute
public class RequireHttpsInProductionAttribute : RequireHttpsAttribute
{
private bool IsProduction { get; }
public RequireHttpsInProductionAttribute(IHostingEnvironment environment)
{
if (environment == null)
throw new ArgumentNullException(nameof(environment));
this.IsProduction = environment.IsProduction();
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (this.IsProduction)
base.OnAuthorization(filterContext);
}
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if(this.IsProduction)
base.HandleNonHttpsRequest(filterContext);
}
}
Design decisions explained:
Use environment IsProduction() or IsDevelopment() over e.g.
"#if DEBUG". Sometimes we release/publish in DEBUG mode on our test server and don't want to disable this security requirement. This needs to be disabled only in localhost/development (since we are too lazy to setup localhost SSL in IIS Express or whatever we use locally).
Use filter in Startup.cs for global setup (since we want this to apply everywhere). Startup should be responsible for registering and setting up all global rules. If your company employ a new developer, she would expect to find global setup in Startup.cs.
Use RequireHttpsAttribute logic since it's proven (by Microsoft). The only thing we want to change is when logic is applied (production) and when it's not (development/localhost). Never use "magical" strings like "http://" and "https://" when it can be avoided by reusing a Microsoft component created to provide the same logic.
Above I would consider the "proper" solution.
Note:
As an alternative, we could make a "class BaseController : Controller" and make all our controllers inherit from "BaseController" (instead of Controller). Then we only have to set the attribute 1 global place (and don't need to register filter in Startup.cs).
Some people prefer the attribute style. Please note this will eliminate design decision #2's benefits.
Example of usage:
[RequireHttpsInProductionAttribute]
public class BaseController : Controller
{
// Maybe you have other shared controller logic..
}
public class HomeController : BaseController
{
// Add endpoints (GET / POST) for Home controller
}

Resources