.Net MVC - Display "session expired" only for private pages - asp.net-mvc-4

I'm developing a .NET MVC 4 application that needs to show a message for "Session expiration" to the user if he performs some action after the session timeout.
I'm using forms authentication with a very long timeout and a sesion with a shorter timeout, so I can know if the session expired or it's a new visitor. This is my Web.config section for that:
<authentication mode="Forms">
<forms loginUrl="~/Login" timeout="10080" defaultUrl="~/Index" />
</authentication>
<sessionState mode="InProc" timeout="20" />
As I said, that's only to differentiate a new visitor from a session timeout, and not necessary for the application itself. It can be changed if the same result can achieved with something else.
With that in the Web.config, I can do the following in the Global.asax:
protected void Session_Start(object sender, EventArgs e)
{
//if it's a new session AND the user is authenticated, then it's a session timeout
if (Request.IsAuthenticated)
{
if (Request.RequestContext.HttpContext.Request.IsAjaxRequest())
{
//...
//Ajax handling code
//...
//The redirect is so I can return a JsonResult with a standard object with the message
Response.Redirect("/GetJson?some_parameters, true);
}
else
{
Response.Redirect("/SessionExpired", true);
}
}
}
All that works great for the private sections of the website that require login. The problem is that if a user is logged, wait for the session timeout, and then try to open a "public" page, one that doesn't require login, he still gets the "Session expired" message. That's what I want to avoid. The "Session expire" message should only be showed to the user if he's trying to access a page that requires log in.
I've tried to set a flag in Session_Start and redirect in a later event of Global.asax, but it didn't work because I had some variable accessibility issues.
I thought of trying to check if the current action has the [AllowAnonymous] attribute so I can skip the redirect, but I couldn't figure out how.
Some answers that I read gave me the impression that my "Session expire" logic should be in a custom attribute but I don't know if that's the right approach.
My question would be, what's the best way of accomplish the "Session expire" for only "private" pages in .Net MVC 4?

As you say, the ideal is to use an attribute. To be more exact this attribute is named "Action Filter".
You can create an ActionFilter implementing an interface, or inheriting an existing one.
In your case I think the easiest way to get your result is to inherit AuthorizeAttribute, and override its OnAuthorization method.
In this method you can access the HttpContext as a property of the filterContext parameter: filterContext.HttpContext. You can check the session, authorization, and check if it's an ajax request using this property. If you need to redirect, you must do it by setting a property of filterContext parameter:
filterContext.Result = new RedirectResult("...url to redirect to...");
You can use any overload of RedirectResult or RedirectToRouteResult constructor.
If you leave the Result property untouched, nothing special will happen.
The you can use this custom attribute instead of the original authorization attribute.

Related

ASP.NET Core : Return Json response on Unauthorized in a filter at the controller/action level

I am not using Identity.
I have this ASP.NET Core configuration enabling two authentication schemes, cookies and basic auth:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "_auth";
options.Cookie.HttpOnly = true;
options.LoginPath = new PathString("/Account/Login");
options.LogoutPath = new PathString("/Account/LogOff");
options.AccessDeniedPath = new PathString("/Account/Login");
options.ExpireTimeSpan = TimeSpan.FromHours(4);
options.SlidingExpiration = true;
})
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
BasicAuthenticationHandler is a custom class inheriting from AuthenticationHandler and overriding HandleAuthenticateAsync to check the request headers for basic authentication challenge, and returns either AuthenticateResult.Fail() or AuthenticateResult.Success() with a ticket and the user claims.
It works fine as is:
Controllers/Actions with the [Authorize] tag will check the cookies and redirect to the login page is not present.
Controllers/Actions with the [Authorize(AuthenticationSchemes = "BasicAuthentication")] tag will check the header and reply a 401 Unauthorized HTTP code if not present.
Controllers/Actions with the [Authorize(AuthenticationSchemes = "BasicAuthentication,Cookies")] tag will allow both methods to access the page, but somehow use the Cookies redirection mechanism when failing both checks.
My goal is to have most of my project to use Cookies (hence why it is set as default), but have some API type of controllers to accept both methods. It should also be possible to tag the Controllers/Actions to return a specific Json body when desired (as opposed to the login redirect or base 401 response), but only for certain controllers.
I've spent the last 2 days reading different similar questions and answers here on StackOverflow, nothing seems to accommodate my need.
Here's a few methods I found:
The options under AddCookie allow you to set certain events, like OnRedirectToAccessDenied and change the response from there. This does not work because it applies to the whole project.
Under my BasicAuthenticationHandler class, the AuthenticationHandler class allow to override HandleChallengeAsync to change the response from there instead of replying 401. Unfortunately, again it applies globally to everywhere you use the scheme, not on a controller/action level. Not sure if it's applied when mixing multiple schemes either.
Many answers point to adding a Middleware to the solution, again, it impacts the whole project.
Many answers point to Policies, but it seems to be to control whether or not an user have access to the resource based on claims, not controlling the response when he do not.
Many answers suggest creating a class inheriting from AuthorizeAttribute, IAuthorizationFilter. Again, this allow to override the OnAuthorization method to decide if the user have the right or not to access the resource, but not to control the response AFTER the normal authentication scheme failed.
I'm thinking either there's a filter type I'm missing, or maybe I need to create a third authentication type that will mix the previous two and control the response from there. Finding a way to add a custom error message in the options would also be nice.
I managed to do it via a IAuthorizationMiddlewareResultHandler. Not my favorite approach because there can be only one per project and it intercepts all calls, but by checking if a specific (empty) attribute is set, I can control the flow:
public class JsonAuthorizationAttribute : Attribute
{
public string Message { get; set; }
}
public class MyAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler DefaultHandler = new AuthorizationMiddlewareResultHandler();
public async Task HandleAsync(RequestDelegate requestDelegate, HttpContext httpContext, AuthorizationPolicy authorizationPolicy, PolicyAuthorizationResult policyAuthorizationResult)
{
// if the authorization was forbidden and the resource had specific attribute, respond as json
if (policyAuthorizationResult.Forbidden)
{
var endpoint = httpContext.GetEndpoint();
var jsonHeader = endpoint?.Metadata?.GetMetadata<JsonAuthorizationAttribute>();
if (jsonHeader != null)
{
var message = "Invalid User Credentials";
if (!string.IsNullOrEmpty(jsonHeader.Message))
message = jsonHeader.Message;
httpContext.Response.StatusCode = 401;
httpContext.Response.ContentType = "application/json";
var jsonResponse = JsonSerializer.Serialize(new
{
error = message
});
await httpContext.Response.WriteAsync(jsonResponse);
return;
}
}
// Fallback to the default implementation.
await DefaultHandler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, policyAuthorizationResult);
}
}
I was typing this on comment... but it's doesn't fit... so here is something we probably need to make clear before choosing a solution:
Authorization process happen at the upper middleware above controller
Yes, AuthorizationMiddleware was registered when we use app.UseAuthorization();, that quite far above controller layer, so it was returned long before the request can reach controller, so, any type of filter cannot be applied here.
Not specify an authentication scheme or policy would easily lead to un-stable behavior.
Imagine, Authentication process return an instance of User that stick with the request, but what would happen if the permission on cookie and basicAuth was difference, like cookie have myclaim, while basicAuth doens't ? Related process on both type of scheme was difference (like challenge on cookie would lead to /Account/Login and basicAuth to /Login ?). And various logic case that we could implement on each page.
I Know, this is not possible, but it would become a mess, not for the author of these code, but for those maintainers to come.
Json response for some specific process on client ?
This might sound detailed at first glance, but it would rather become burden soon, if some more authentication requirement raise after that (like Jwt). Covering each of these case on client would make user experience quite awkward (like, half-authentication and authorization).
And if It's un-avoidable in the project. Might I suggest create a default authentication scheme with ForwardDefaultSelector that would elected which authentication scheme to use for each request. And maintain a stable routing HashSet that would use to detect on which endpoint to set Json Response as wished on some upper level than AuthorizationMiddleware, by using middleware, ofcourse. Then, we narrow down to 2 centralize places to checkout the authorization.
Chaos came when we tried to make one thing to do somethings. At least in this case, I think we would breath easier when coming to debug phase.

Logging out the user from other computers (that he logged in before) when he logs in from another computer

I have a web application that employees log in to do stuff. What Im trying to achieve is: When a user logs in from a computer, if he is already logged in on another computer, that must be logged out. The web app is MVC Asp.Net Core 2.2 Code first. I have added a signInManager in startup and edited the PasswordSignInAsync method. I login the system from two different devices. When I click something on the screen from the first computer that I loggedin, it redirects to logout. It seems like working. But Im not sure if this is the right way of doing this. The code I added is: await UserManager.UpdateSecurityStampAsync(user); Inside PasswordSignInAsync method.
Inside the startup class ConfigureServices method I added
'services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddSignInManager<SignInManagerX>()'
Then in SignInManagerX class which is inherited from SignInManager I overrided the PasswordSignInAsync
public override async Task<SignInResult>
PasswordSignInAsync(ApplicationUser user, string password,
bool isPersistent, bool lockoutOnFailure)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var attempt = await CheckPasswordSignInAsync(user, password,
lockoutOnFailure);
//Here is added
if (attempt.Succeeded)
{
await UserManager.UpdateSecurityStampAsync(user);
}
//Add end
return attempt.Succeeded
? await SignInOrTwoFactorAsync(user, isPersistent)
: attempt;
}
Is this the right way ? or I should add a table to db for logins which holds the info if the user is already logged in on another Ip. Then Logging out that user from all computers if the last and current login attempt is true ?
Yes , the primary purpose of the SecurityStamp is to enable sign out everywhere.
The basic idea is that whenever something security related is changed on the user, like a password, it is a good idea to automatically invalidate any existing sign in cookies, so if your password/account was previously compromised, the attacker no longer has access.
Reference : https://stackoverflow.com/a/19505060/5751404
You can set validateInterval to TimeSpan.Zero for immediate logout .

Invoke a Controller Action from an Interceptor on Asp.Net MVC (Castle Windsor)

Is there any way this. I want to invoke an action with parameter (or parameterless at least) like below.
My situation is;
Interceptor not contains any reference from MVC, Interceptor is located at ApplicationService layer.
This is a service Interceptor.
public class ControllerInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var retVal = (ResponseDTOBase) invocation.ReturnValue;
if (retVal.ResponseCode == UsrNotAuth)
{
//Invoke Controller Action With passsing parameter (retVal)
}
invocation.Proceed();
}
}
Any ideas ? Thanks.
May I offer you another approach for request authorization. MVC is a state machine in its core principle. State machines have actions, triggers and guards. There is already such a 'guard' in MVC for the very purpose of intercepting controller actions and checking for the user privileges. This is the AuthorizeAttribute. This class implements IAuthorizationFilter. Another aspect is that authorization and authentication should happen before they reach your services. What I mean exactly is that there are two types of authorization :
Action Authorization and
Data Authorization.
The first type of authorization you can implement with AuthorizeAttribute or your custom attribute implementation of IAuthorizationFilter + FilterAttribute. Here is an example implementation of such an attribute for a SPA (Single Page Application) that works with ajax requests :
The attribute :
[AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class LoggedOrAuthorizedAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
CheckIfUserIsAuthenticated(filterContext);
}
private void CheckIfUserIsAuthenticated(AuthorizationContext filterContext)
{
// If Result is null, we’re OK: the user is authenticated and authorized.
if (filterContext.Result == null)
return;
// If here, you’re getting an HTTP 401 status code. In particular,
// filterContext.Result is of HttpUnauthorizedResult type. Check Ajax here.
// User is logged in but this operation is not allowed
if (filterContext.HttpContext.User.Identity.IsAuthenticated && filterContext.HttpContext.Request.IsAjaxRequest())
{
//filterContext.HttpContext.Response.StatusCode = 401;
JsonNetResult jsonNetResult = new JsonNetResult();
jsonNetResult.Data = JsonUtils.CreateJsonResponse(ResponseMessageType.info, "msgOperationForbiddenYouAreNotInRole");
filterContext.Result = jsonNetResult;
//filterContext.HttpContext.Response.End();
}
}
}
If you use pure MVC there is an example implementation here.
The usage :
In your controller
[LoggedOrAuthorized(Roles = Model.Security.Roles.MyEntity.Create)]
public ActionResult CreateMyEntity(MyEntityDto myEntityDto)
{
...
}
You can apply this on every controller action and block the user even before the controller is reached.
You can supply Loggers and other 'plumbing' through Castle Windsor inside your filters in order to record the events.
A very good and important links and comments are available in this answer of a similar question. These links provide very good guide for proper implementation too.
The other type of authorization - Data Access Authorization can be handled in the service or in the controller. I personally prefer to handle all kinds of authorization as soon as possible in the pipeline.
General practice is not to show to the user any data or action that he is not authorize to view or to execute commands upon it. Of course you have to double check this because the user can modify the POST and GET requests.
You can make simple interface with implementation IDataAccessService and control data access by passing user id and entity id to it.
Important thing is that you should not throw exception when the user is not authorized because this is no exception at all. Exception means that your program is in an unexpected state which prohibits its normal execution. When a user is not authorized this is not something unexpected - it is very well expected. That is why in the example implementation a message is returned rather then exception.
Another subtlety is that "exceptions" are handled differently by the .NET framework and they cost a lot more resources. This means that your site will be very vulnerable to easy DDOS outages or even they can perform not as they can. General rule is that if you control your expected program flow through exceptions you are not doing it properly - redesign is the cure.
I hope this guides you to the right implementation in your scenario.
Please provide the type of the authorization you want to achieve and parameters you have at hand so I can suggest a more specific implementation.

ASP.NET MVC 4 Read Persistent Auth Cookie Event?

I'm using WebSecurity for Authentication and a custom ClaimsAuthorizationManager for Authorization.
Mixing Claims based authorization with the built in WebSecurity features for Authentication has provided me with a ton of value. I highly recommend it for anyone who requires complex authorization logic combining from several systems.
Anyways, everything is working great except the RememberMe feature.
When a user logs in, I set my auth cookie (via WebSecurity), new up my ClaimsPrincipal, and write it to my SessionSecurityToken. Bam, it works brilliantly.
However, when a user has previously elected to persist the (Websecurity) auth cookie, she is allowed to bypass my login method, which news up my ClaimsPrincipal and writes my principal to my SessionSecurityToken. My authorization fails because my claims haven't been loaded, because I haven't had a chance to transform my ClaimsPrincipal.
Is there a way to hook into a (Websecurity) "forms authentication cookie read" event? If so, I could handle it, new up my ClaimsPrincipal, and be on my way. Thanks in advance!
You could write a custom AuthorizeAttribute:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (authorized)
{
httpContext.User = new ClaimsPrincipal(...)
}
return authorized;
}
}
Now decorate your protected controller actions with this custom attribute instead of the default built-in:
[MyAUthorize]
public ActionResult Protected()
{
...
}

WebForms - what event to use to authenticate ticket passed in the request body?

That might sound quite trivial, but I could not find a good answer: when the request is coming with a validation ticket in the body of the request, what event is best suited to authenticate the request (and then create FormsAuthenticationTicket and auth cookie for subsequent calls)?
One option is in Page_PreInit in BasePage, another Application_AuthenticateRequest in Global.asax, and yet another FormsAuthentication_OnAuthenticate in Global.asax.
Any link pointing to the solution will be very helpful.
Pawel
I have not been able to find a link with a definitive answer on this, but from personal experience and from reading the ASP.NET Application Life Cycle it seems that Application_BeginRequest is the best option. I have an application in production for several years using this event for the scenario you describe (transforming an application-specific ticket into an ASP.NET forms authentication ticket).
The problem with using Application_AuthenticateRequest and the others you mention is that it will be too late for controls later in that same request cycle to use the forms authentication cookie that you create.
Here is a simple example. You'd need to fill your custom logic for how the ticket gets validated.
protected void Application_BeginRequest(Object sender, EventArgs e)
{
if (!GetRequestHasValidTicket() )
{
string bodyToken = Request.Form["MyTokenName"];
//custom logic to authenticate token
bool tokenIsValid = true;
if (tokenIsValid)
{
System.Web.Security.FormsAuthentication.SetAuthCookie("myusername", false);
}
}
}
private bool GetRequestHasValidTicket()
{
FormsAuthenticationTicket ticket = null;
try
{
ticket = System.Web.Security.FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value);
}
catch { }
return ticket != null;
}
A small problem with BeginRequest is that Context.User is not set yet, so you have to manually check for the valid ticket in order to avoid adding the forms auth ticket to every response. If your application logic is such that the ticket only shows up in the body of the very first request, then you might not need this extra check.