In my MVC 5 web application when the user session expires and the user clicks in something in the page that does an AJAX call I was still getting response status 200 with X-Responded-JSON:401.
I have then read and investigated and found a way to solve the problem.
So...added to the Startup class ConfigureAuth method this
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider =
new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(TimeSpan.FromMinutes(15), (manager, user) => user.GenerateUserIdentityAsync(manager)),
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
ctx.Response.Redirect(ctx.RedirectUri);
}
}
},
SlidingExpiration = false,
ExpireTimeSpan = TimeSpan.FromMinutes(30)
});
Just a helper...
private static bool IsAjaxRequest(IOwinRequest request)
{
IReadableStringCollection query = request.Query;
if ((query != null) && (query["X-Requested-With"] == "XMLHttpRequest"))
{
return true;
}
IHeaderDictionary headers = request.Headers;
return (headers != null) && (headers["X-Requested-With"] == "XMLHttpRequest");
}
This solved my problem locally (in my dev machine) and I am now able to see the 401 in the Ajax calls when the sessions expires or the user logs or when is still trying to access something in a open tab.
My problem is that this work only locally. When I have my application in a server this Ajax calls still return 200 and I do not understand why....however if I access the application inside of the box everything works as expected and I can see the 401 in the browser
Can someone maybe know a better solution for this or maybe the reason why this works locally but not in the server?
Many thanks
The solution posted in the initial question works as expected. This approach was not working in the beginning because of the custom error pages that you can define in the web.config. It was messing up with the initial solution that I found in the web.
<system.web>
<globalization enableClientBasedCulture="true" culture="en-GB" uiCulture="auto" />
<customErrors mode="Off" defaultRedirect="~/Error/Index">
<error statusCode="400" redirect="~/Error/Index" />
<error statusCode="401" redirect="~/Error/Index" />
...
...
This was redirecting to a different page and because of that was returning 200 instead of 401.
Thanks
Related
I have the following requirements
Set a value in web.config and enable maintenance mode
All non-ajax requests should be shown a custom error page, with the http status code set to 503. The url of page should be retained.
All ajax requests should be responded with http status code 503
I should have an opportunity to do some basic logging to a file. Log the url and the user Identity if he happened to be logged into the app
I am using ELMAH to track/log all unhandled exceptions. The mechanism for implementing maintenance mode shouldn't need me to not use ELMAH
I have "runAllManagedModulesForAllRequests" set to true. this was originally done for use with RequestReduce. we no longer use it, but I am hesitant to reset its value to false. I am not sure if any other library needs it.
Once I realized there is nothing built in which supports the above requirements, I felt I had the following two options in front of me (App_offile.html won't work for me).
an HttpModule
an MVC ActionFilter
I dropped the MVC ActionFilter as I couldn't figure out how to guarantee it to run before any authentication/authorization filters. I have a custom authentication filter which hits the db. The idea behind the maintenance mode is the db might be offline, yet the web-app shouldn't show a 500 custom error page, but a 503 custom error page.
I wrote the following httpmodule and added in my web.config. It works for ajax requests. It kinda works for non-ajax requests. All requests get redirected to the 503 error page. The side-effect is all requests for static content also result in a 503. My error page thus is shown unstyled :(
// the http module
public class MaintenanceModeModule : IHttpModule
{
private static bool _isUnderMaintenance;
static MaintenanceModeModule()
{
var valueStr = (ConfigurationManager.AppSettings["UnderMaintenance"] ?? (false).ToString());
bool underMaintenance;
bool.TryParse(valueStr, out underMaintenance);
_isUnderMaintenance = underMaintenance;
}
public void Init(HttpApplication application)
{
application.BeginRequest += OnBeginRequest;
}
private void OnBeginRequest(object sender, EventArgs e)
{
var application = (HttpApplication) sender;
var request = application.Request;
var response = application.Response;
if (_isUnderMaintenance == false)
{
return;
}
application.Context.Items["under_maintenance"] = true; // used later
if (request.Url.PathAndQuery == "/503") // the url of the action that renders the custom error page
{
return;
}
const int statusCode = (int) HttpStatusCode.ServiceUnavailable;
const string statusMessage = "Temporarily down for maintenance";
var requestWrapper = new HttpRequestWrapper(request);
if (requestWrapper.IsAjaxRequest())
{
response.Clear();
response.ClearContent();
response.ClearHeaders();
response.StatusCode = statusCode;
response.TrySkipIisCustomErrors = true;
response.StatusDescription = statusMessage;
response.End();
return;
}
// doesn't work, shows the Yellow Screen of Death (YSoD)
// application.Context.Server.Transfer("~/503", preserveForm: true);
// doesn't work, shows the Yellow Screen of Death (YSoD)
// throw new HttpException(statusCode, statusMessage);
response.Redirect("~/503");
}
public void Dispose()
{
}
}
...
// web.config
// only the relevant portions of each section is shown
<appSettings>
<add key="UnderMaintenance" value="true" />
</appSettings>
<customErrors mode="On"> <!-- Custom errors are on, even then I was seeing YSoDs during my attempts -->
<error statusCode="404" redirect="404" />
<error statusCode="503" redirect="503" />
</customErrors>
<system.webServer>
<httpErrors existingResponse="PassThrough">
</httpErrors>
<modules runAllManagedModulesForAllRequests="true">
<add name="MaintenanceMode" type="WebApp.Code.MvcInfrastructure.MaintenanceModeModule" />
</modules>
</system.webServer>
...
// route config
routes.MapRoute("503Error", "503", new { controller = "Error", action = "UnderMaintenance" });
...
// error controller
// the authentication filter skips authentication if the allowanonymous attribute is present
[AllowAnonymous]
public class ErrorController : CustomBaseController
{
public ErrorController(AppConfig appConfig)
: base(appConfig)
{
}
public ActionResult UnderMaintenance()
{
// behind the scenes reads the value from HttpContext.Items.
// This was set during the execution of the httpmodule
if (AppConfig.UnderMaintenance == false)
{
return new RedirectResult("~/");
}
Response.StatusCode = (int) HttpStatusCode.ServiceUnavailable;
Response.TrySkipIisCustomErrors = true;
// the actual content of the view is not relevant now
return View("Error503");
}
}
The problems with this approach,
Each non-ajax request is responded with a 302 and then a 503
The URL requested by the browser is not retained
It returns a 503 for all static assets as well
The code I wrote and web.config settings I enabled are all cobbled together from various sources. I am not fully sure what those settings do or what the recommended way is. Please feel free to answer with a completely different method, as long as it can meet the requirements stated.
hi i used form authentication and create ticket and add it to Response it works good.
when i look at created cookies by Firefox tools>pageinfo>security>cookies find that the expire time set currently on cookies .
it works good on local but when i upload it on server (server 2008-iis7) ,the expire time
not works but sets on cookie and always my cookie expires about 10 minus and member will
log out.
has it some specially settings,i look at some example on internet but cant not find anythings.
my authentication codes:
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
username,//username
DateTime.Now,
DateTime.Now.AddMinutes(120),
true,
rollname,
FormsAuthentication.FormsCookiePath);
string hashCookies = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName,hashCookies); // Hashed ticket
cookie.Expires = DateTime.Now.AddMinutes(120);
Response.Cookies.Add(cookie);
i used this method in global.asax to check requests authentication:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// look if any security information exists for this request
if (System.Web.HttpContext.Current.User != null)
{
// see if this user is authenticated, any authenticated cookie (ticket) exists for this user
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
// see if the authentication is done using FormsAuthentication
if (System.Web.HttpContext.Current.User.Identity is FormsIdentity)
{
// Get the roles stored for this request from the ticket
// get the identity of the user
FormsIdentity identity = (FormsIdentity)System.Web.HttpContext.Current.User.Identity;
// get the forms authetication ticket of the user
FormsAuthenticationTicket ticket = identity.Ticket;
// get the roles stored as UserData into the ticket
string[] roles = ticket.UserData.Split(',');
// create generic principal and assign it to the current request
System.Web.HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(identity, roles);
}
}
}
}
in my web config:
<authentication mode="Forms">
<forms loginUrl="~/Home.aspx" timeout="120" />
</authentication>
I am building an MVC 3 application with an IIS 7.5 backend. On my controller, I have action methods that allow the user to add/edit domain objects. The Action handles HTTP Post, has a return value of string which contains any validation error messages encountered during the save process. Here is an example of one Action Method:
[HttpPost]
public string CustomerEdit(CustomerModel customerModel)
{
var errorMessages = new StringBuilder();
var serverErrors = new List<string>();
//Map to a customer domain object
Mapper.CreateMap<CustomerModel, Customer>();
var customer = Mapper.Map<CustomerModel, Customer>(customerModel);
if (customerModel.Oper == "del")
{
var customerIds = new List<Guid>();
customerIds.Add(customer.Id);
if (!_serverService.DeleteCustomers(customerIds))
{
errorMessages.Append("The item could not be deleted");
Response.StatusCode = Constants.STATUS_SERVER_ERROR;
}
}
else
{
//Validate
if (!_serverService.ValidateCustomer(customer, out serverErrors))
{
foreach (var error in serverErrors)
{
ModelState.AddModelError("Validation", error);
}
}
//Save
if (ModelState.IsValid)
{
var saveStatus = _serverService.SaveCustomer(ref customer, _id);
if (!saveStatus)
{
errorMessages.Append("The server encountered and error during Save");
Response.StatusCode = Constants.STATUS_SERVER_ERROR;
}
}
else
{
errorMessages.Append(GetValidationErrors(ModelState));
Response.StatusCode = Constants.STATUS_SERVER_ERROR;
}
}
return errorMessages.ToString();
}
In the case of an error, I need to set the Response.StatusCode property to a value of either 400/500, and return a concatenated string of detailed error messages. Unfortunately, IIS always strips my error string out of the response test, and (in the case of 400 errors) adds replaces it with the string 'Bad Request'
Is there a way to configure IIS to return a custom, Action-specific, string when the status code is set to 400?
After talking to a friend of mine who is a wiz at configuring IIS, I found that in IIS 7+ you can add the following to web.config:
<system.webServer>
<httpErrors errorMode="Detailed"/>
</system.webServer>
If this setting in web.config is used, AND you set the body of the response, then the response body will reach the client. If you do NOT set the response body, then IIS will serve up a detailed error page with detailed error information (see http://learn.iis.net/page.aspx/267/how-to-use-http-detailed-errors-in-iis/). Many folks consider this a security risk, so use with caution.
In MVC it is also possible to do this on a Action by Action basis. See TrySkipisCustomErrors.
Use:
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return this.Json(SomeJsonObject); // could output string too
we ended up with going for
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="PassThrough" />
</system.webServer>
in the web.config. Which allowed custom errors that set status codes to leave IIS unmolested.
At least the page Configuring HTTP Error Responses in IIS 7 says
Note
You cannot customize the following HTTP error messages: 400,
403.9, 411, 414, 500, 500.11, 500.14, 500.15, 501, 503, and 505.
EDIT: though in the responses of this question, which looks quite similar, there is a response claiming that at least something can be done with httpErrors configuration.
I have a scenario where I have to handle authentication of ajax requests using "Forms Authentication". Based on some search and help from my earlier stackoverflow post, I had decided to use the method described at here.
The idea is to send back a 401 response for unauthenticated requests, and then handle that in the AJAX error handler. So I have an AJAX error handler in my ASP.net MVC3 Layout page that redirects the browser to the login page when it receives 401 response on unauthenticated ajax requests. Here is the ajax error handler.
$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) {
if (jqXHR.status == "401") {
window.location.replace(loginUrl);
}
....
});
This all works well on my local IIS 7.5 Server. But on the server where my site is hosted, unfortunately, I get a basic authentication popup on unauthenticated ajax requests (for example session timed out), before the AJAX error handler runs and redirects the browser to the login page. When I cancel the "Authentication Required" popup by pressing the Cancel button, the AJAX error handler then runs and I am redirected to the login page.
So, why does the browser show the authentication popup before running the AJAX error handler?
Edit: The Hosting Server is running IIS 6.
as Softlion said
This is a common question with an easy answer. the 401 is transformed into a 302 to the login >page by the .net authorization module. The browser never see the 401 only the 302.
if you are using .net 4 and later, you use code below
HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
it work's fine for me.
This is a common question with an easy answer.
the 401 is transformed into a 302 to the login page by the .net authorization module. The browser never see the 401 only the 302.
Of course this is not playing nicely with ajax calls.
The best solution i tryed and i'm currently using involve writing a new attribute which is catching 401 and tranform it into ... 409 which is catched by the jquery ajax handler.
It is part of a paid product so i can not give any code.
Try to remove WWW-Authenticate header from response.
IIS 6 in integrated mode? I don't believe there is any such thing, unless you're talking about integrated authentication.
My guess is that you're using a non-aspx extension, so on IIS6 this means that it's not even hitting the .net process. So, IIS is using it's own 401 error response page.
Likely, the solution is to force all requests to be handled by the .net process.
Your host will have to go into IIS properties > configuration > wildcard mappings - and map everything to the .net process.
.net won't catch the 401 errors. What I did was to set the IIS error page from the default 401 page to my own static 401 page. From that page I used javascript to redirect to another handler.
The solution here is to write a custom HttpModule to workaround the MVC frameworks default behavior. Once I was finally able to register the module (cheers David Ebbo) it worked for me. You may want to choose your own criteria for calling SuppressAuthenticationRedirect.
public class SuppressFormsAuthenticationRedirectModule : IHttpModule {
private static readonly object SuppressAuthenticationKey = new Object();
public static void SuppressAuthenticationRedirect(HttpContext context) {
context.Items[SuppressAuthenticationKey] = true;
}
public static void SuppressAuthenticationRedirect(HttpContextBase context) {
context.Items[SuppressAuthenticationKey] = true;
}
public void Init(HttpApplication context) {
context.PostReleaseRequestState += OnPostReleaseRequestState;
context.EndRequest += OnEndRequest;
}
private void OnPostReleaseRequestState(object source, EventArgs args) {
var context = (HttpApplication)source;
var response = context.Response;
var request = context.Request;
if (response.StatusCode == 401 && request.Headers["X-Requested-With"] ==
"XMLHttpRequest") {
SuppressAuthenticationRedirect(context.Context);
}
}
private void OnEndRequest(object source, EventArgs args) {
var context = (HttpApplication)source;
var response = context.Response;
if (context.Context.Items.Contains(SuppressAuthenticationKey)) {
response.TrySkipIisCustomErrors = true;
response.ClearContent();
response.StatusCode = 401;
response.RedirectLocation = null;
}
}
public void Dispose() {
}
public static void Register() {
DynamicModuleUtility.RegisterModule(
typeof(SuppressFormsAuthenticationRedirectModule));
}
}
For me this ended up being simple. Most IIS web sites with anonymous authentication also have a default Windows Auth enabled
Turn off the Windows Authentication which is what pops up the login screen when the site detects the 401 even from an ajax call.
<security>
<authentication>
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="false" />
</authentication>
</security>
Wait ! I thought you said Ajax request, how can you get a popup on ajax request ? I am pretty sure somewhere else you are triggering the call to the URL even before AJAX call. From your scenario its proved that when you cancel the popup, your actual ajax request is being made and hence you can do a ajax redirect.
The idea is to send back a 401 response for unauthenticated requests, and then handle that in the AJAX error handler
You can get an ajax response only if you send a ajax request, if you send normal http request then you will get a popup. This has nothing to do with .Net or Java :)
I'm using FormsAuthentication, I'm having problems setting the TimeOut value.
I've seen some other posts related to this, but they don't seem to be exactly my problem or the solution suggested doesn't help.
My web.config has the following:
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn"
timeout="1"
cookieless="UseCookies" />
</authentication>
I have put an AuthorizeAttribute on the controllers that I want to secure.
I can view the .ASPXAUTH cookie (using FireCookies) and I can see that it is set to expire in 1 minute after a login.
If I debug through my code I can see that FormsAuthentication.Timeout = 1.
However my ticket doesn't seem to timeout in 1 minute. After 1 minute of inactivity I can still browse to controllers with AuthorizeAttribute.
In fact I can actually delete the .ASPXAUTH cookie using FireCookies and I can still browse to controllers with an AuthorizeAttribute.
Bizarrely after being inactive for a long time (sorry don't have an exact time - I was out for lunch!) the TimeOut occurs and I am redirected
to the login screen.
Any ideas?
I too had the same problem. Actually, it came about because I could not read the forms authentication cookie from javascript, it was after a while undefined. I just wanted to know if I was authenticated via javascript.
Later I found that the ticket had expired, but I was not getting logged out (also. so i wanted to solve that too)! I saw your question had not been answered so I kept it open while I worked out my problems for half a day. The following is what I came up with that appears to be working.
My Answer is based on this answer. https://stackoverflow.com/a/454639/511438 by user ScottS
This is in my ASP.NET MVC 3 project.
Here is my login code. Not shown, the custom user authentication logic before it. This just sets the initial ticket.
public class FormsAuthenticationService : IFormsAuthentication
public void SignIn(string userName, bool createPersistentCookie, string role)
{
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1, // version
userName, // user name
DateTime.Now, // created
DateTime.Now.Add(FormsAuthentication.Timeout), // expires
false, // rememberMe?
role // can be used to store roles
);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContext.Current.Response.Cookies.Add(authCookie);
}
in the same class but a static method that is accessed from global.asax
//-- this is based on https://stackoverflow.com/questions/454616/asp-net-cookies-authentication-and-session-timeouts
internal static FormsAuthenticationTicket RefreshLoginCookie(bool retainCurrentExpiry)
{
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == null)
return null;
FormsAuthenticationTicket oldTicket = FormsAuthentication.Decrypt(authCookie.Value);
DateTime expiryDate = (retainCurrentExpiry ? oldTicket.Expiration : DateTime.Now.Add(FormsAuthentication.Timeout));
HttpContext.Current.Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
var newTicket = new FormsAuthenticationTicket(oldTicket.Version, oldTicket.Name, oldTicket.IssueDate, expiryDate,
oldTicket.IsPersistent, oldTicket.UserData, oldTicket.CookiePath);
HttpCookie newAuthCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(newTicket));
HttpContext.Current.Response.Cookies.Add(newAuthCookie);
return newTicket;
}
Global.asax
My customization comes that ajax requests do not refresh the forms authentication ticket. So if you sit there for the timeout period, an ajax request will log you out. Change that if you want ajax requests to keep the ticket alive (addresses my javascript cookie issue, not your logout inactivity issue).
*(tip, if you get logged out, then login, but come back to the login page again, the first login did not specify a returnUrl in the querystring perhaps). *
protected virtual void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == "")
{
return;
}
bool isAjax = new HttpRequestWrapper(System.Web.HttpContext.Current.Request).IsAjaxRequest();
FormsAuthenticationTicket authTicket;
try
{
//-- THIS IS WHAT YOU WANT
authTicket = FormsAuthenticationService.RefreshLoginCookie(isAjax);
}
catch
{
return;
}
string[] roles = authTicket.UserData.Split(';');
if (Context.User != null) Context.User = new GenericPrincipal(Context.User.Identity, roles);
}
Web.config
here is the part where i set session timeout and ticket timeout
<configuration>
<system.web>
<sessionState mode="InProc" timeout="60" />
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="60" name="ProviderMvcSession" cookieless="UseCookies" />
</authentication>
An obvious solution that could easily be looked over by some people, clearing the browser cookies or logging out should solve the issue. If remember me is checked, the application will keep logging in with the old cookie regardless the changes made to the Web.Config timeout value.
It's about how long is the timeout of your session. Default is 20 minutes and you can change it in web.config like this:
<sessionState mode="InProc" timeout="20"/>