Sliding Session Expiration with ServiceStack Authentication on ASP.NET MVC - 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());
}

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());
}

MVC Removing ApplicationCookie from Response

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.

How would you 'force' users to fill out a profile before viewing ASP.NET MVC site?

After my users register the first time, I want them to have to fill out a profile page within the website. I have it set so that it redirects them during log-in if they have not filled out the profile before, but if they type in another url within the website they are currently free to go anywhere they want after that redirect.
What is the best way to require users to the profile page when they try to visit any page on my site until they have completed the profile?
Is this best done with something like: 'if (!user is verified) - redirect to profile page' placed at the top of every controller? Is there a more elegant solution?
Start with implementing a custom Action Filter (IActionFilter):
public class ProfileRequiredActionFilter : IActionFilter
{
#region Implementation of IActionFilter
public void OnActionExecuting(ActionExecutingContext filterContext)
{
//TODO: Check if the Authenticated User has a profile.
//If Authenicated User doesn't have a profile...
filterContext.Result = new RedirectResult("Path-To-Create-A-Profile");
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
#endregion
}
Then register the Action Filter globally inside the RegisterGlobalFilters method of the Global.asax...
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ProfileRequiredActionFilter());
}
Note: If you don't want this filter to be applied globally, you can create an ActionFilterAttribute instead and apply it to Controllers and/or Action methods...
public class ProfileRequiredAttribute : ActionFilterAttribute
{
#region Implementation of IActionFilter
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
//TODO: Check if the Authenticated User has a profile.
//If Authenicated User doesn't have a profile...
filterContext.Result = new RedirectResult("Path-To-Create-A-Profile");
}
#endregion
}
You could create a Base controller and have all your other controllers inherit from that.
Then have an OnActionExecuting method in it with something like...
protected override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
// If the user has not filled out their profile, redirect them
if(CurrentUser != null && !CurrentUser.IsVerified)
{
context.Result = new RedirectResult("/User/Profile/" + CurrentUser.ID);
}
}

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