ASP NET MVC5 randomly redirects to my login page - asp.net-mvc

I have an ASP.NET MVC application with ActionFilters for Authentication and no Forms Authentication. "SegurancaAction" is the attribute responsible for validating authentication and exists in every controller endpoint except in the login ones (as expected).
I'm facing a problem in which sometimes I try to access one of my controllers and the GET request goes to my login endpoint. In the method Application_BeginRequest at Global.asax, I can see the very first attempt is at 'security/login' (the route to my login endpoint) instead of the one I want. I can also see this endpoint being called in debugging apps such as Fiddler, or ASP.NET Trace or Glimpse MVC5.
Besides calling the wrong action, once I login again this issue keeps happening for the same endpoint I was trying to access, redirecting my site to the login page over and over.
SegurancaAction:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Autenticacoes autenticacao = _authApp.IsAutenticado(filterContext.HttpContext.Session.SessionID);
if (autenticacao == null)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
filterContext.Result = new HttpStatusCodeResult(System.Net.HttpStatusCode.Unauthorized);
else
{
filterContext.HttpContext.Response.RedirectPermanent("/security/login");
return;
}
}
else
{
// other stuff
}
}
SecurityController:
[HttpPost]
[ConfigAction]
public ActionResult Login(vm_Login login)
{
if (ModelState.IsValid)
{
if (!String.IsNullOrEmpty(login.Login) && !String.IsNullOrEmpty(login.Senha))
{
Entidades entidade = _entidadeApp.GetByUsuarioSenha(login.Login, login.Senha);
if (entidade == null)
{
ViewBag.FalhaAutenticacao = "As credenciais informadas não conferem!";
return View("Login");
}
else
{
string encryptionKey = System.Configuration.ConfigurationManager.AppSettings["EncryptionKey"];
var a = _autenticacaoApp.Autenticar(entidade.Id, encryptionKey, login.Senha, HttpContext.Session.SessionID);
}
Response.RedirectPermanent("~/principal/index");
}
}
else
{
ViewBag.FalhaAutenticacao = "É necessário informar o usuario e a senha!";
}
return View();
}
All _autenticacaoApp.Autenticar(...) method does is to create an authentication entry on the database, it's a completely custom code.
Does anyone know why this issue happens? Sometimes I can reproduce it by deleting the cookies that contain ASP.NET_Session ID and RequestVerificationToken. So far I know those cookies are automatically generated and I notice that sometimes when I login again they are not re-generated.

I figured out the issue. It was this "RedirectPermanent" method being used here:
filterContext.HttpContext.Response.RedirectPermanent("/security/login");
It tells the browser that the resource I'm trying to access is no longer available and is now located at this new Url. The browser records this information and always redirects to the new resource.
I just changed it to use "Redirect" instead.

Related

Website Slow At Loading The Login Form

I have an ASP.NET MVC website which I am developing, and I recently deployed it on a test server so the client could give some initial feedback.
I have noticed that on the live server there is a considerable delay in loading the login form, of around 20-30 seconds. Once you are logged in, the system functions just fine, and is responsive.
If you log out, and return to the login page, it is once again slow.
It doesn't appear to be an issue with the apppool spooling up, as it happens every time on the page, not just once, and all items appear to be loading.
Any suggestions on how to debug this?
Below is the BaseController from which all controllers enherit, plus the account login controller.
protected override void ExecuteCore()
{
if (User.Identity.IsAuthenticated)
{
try
{
AccountDataContext = new AccountDAL.DataContext(ConfigurationManager.AppSettings["Server"]);
// set the current user.
CurrentUser = AccountDataContext.Users.FirstOrDefault(x => x.Email == User.Identity.Name);
AccountDataContext.CurrentAccount = CurrentUser.Account;
ViewBag.CurrentUser = CurrentUser;
ViewBag.Account = CurrentUser.Account;
SystemDataContext = new SystemDAL.DataContext(ConfigurationManager.AppSettings["Server"], CurrentUser.Account.Database);
// setup the account based on the users settings
ViewBag.Theme = "Default"; // hard coded for now
}
catch (Exception)
{
// if the previous threw an exception, then the logged in user has been deleted
// log them out
FormsAuthentication.SignOut();
Session.Abandon();
// clear the authentication cookie
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, "");
cookie.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(cookie);
FormsAuthentication.RedirectToLoginPage();
}
}
base.ExecuteCore();
}
and the Accounts controller:
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if(AccountDataContext == null)
AccountDataContext = new AccountDAL.DataContext(ConfigurationManager.AppSettings["Server"]);
var user = AccountDataContext.Users.FirstOrDefault(x => x.Email == model.UserName && x.Password == model.Password);
if (user != null)
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Couple of things will improve performance:
First and foremost : Deploy your site in Release mode if you care anything at all about performance. Here a great article from Dave Ward about it.
<compilation targetFramework="your framework version" debug="false">
If you are not using webforms view engine (Which i assume you are not) jus disable it and use Razor only and to take it a little further, allow just chtml files
ViewEngines.Engines.Clear();
IViewEngine RazorEngine = new RazorViewEngine() { FileExtensions = new string[] { "cshtml" } };
ViewEngines.Engines.Add(RazorEngine);
Configure Idle Time-out Settings for an Application Pool (IIS 7) Here's the link
EDIT1:
Based on your last comment mentioning that the app is running fine in your local IIS. I would recommend that you start focusing on analyzing the requests on your remote IIS , here's a link to a tool that you can use.
In order to trace successful requests as well (And you should do that in your case) set the status to 200 here's a tutorial on that as well.
As the slowdown only seems to affect the login pages, I took a closer look at the Account controller which they both use (no other account functions implemented at this time).
I found the following code at the top of the controller:
public AccountController()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
Removing this declaration cured the issue. I'm not sure what it is or where it came from, I think it's from the original MS default implementation authentication. I don't have an ApplicationDbContext, so I think it was waiting on this request to time out before proceeding.
Comment out your data access code and run it. Does that speed things up? Or add Debug.WriteLine(System.DateTime.Now) and see where the long gap is. There might be a delay connecting to the database
In application pool setting There is setting called Load User profile. Set it to true it will help you to make your site faster.

Serving an iCalendar file in ASPNET MVC with authentication

I'm trying to serve an iCalendar file (.ics) in my MVC application.
So far it's working fine. I have an iPhone subscribing to the URL for the calendar but now I need to serve a personalised calendar to each user.
When subscribing to the calendar on the iPhone I can enter a username and password, but I don't know how to access these in my MVC app.
Where can I find details of how the authentication works, and how to implement it?
It turns out that Basic Authentication is what is required. I half had it working but my IIS configuration got in the way. So, simply returning a 401 response when there is no Authorization header causes the client (e.g. iPhone) to require a username/password to subscribe to the calendar.
On the authorization of the request where there is an Authorization request header, the basic authentication can be processed, retrieving the username and password from the base 64 encoded string.
Here's some useful code for MVC:
public class BasicAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var auth = filterContext.HttpContext.Request.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var encodedDataAsBytes = Convert.FromBase64String(auth.Replace("Basic ", ""));
var value = Encoding.ASCII.GetString(encodedDataAsBytes);
var username = value.Substring(0, value.IndexOf(':'));
var password = value.Substring(value.IndexOf(':') + 1);
if (MembershipService.ValidateUser(username, password))
{
filterContext.HttpContext.User = new GenericPrincipal(new GenericIdentity(username), null);
}
else
{
filterContext.Result = new HttpStatusCodeResult(401);
}
}
else
{
if (AuthorizeCore(filterContext.HttpContext))
{
var cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null);
}
else
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusDescription = "Unauthorized";
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic realm=\"Secure Calendar\"");
filterContext.HttpContext.Response.Write("401, please authenticate");
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.Result = new EmptyResult();
filterContext.HttpContext.Response.End();
}
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
Then, my controller action looks like this:
[BasicAuthorize]
public ActionResult Calendar()
{
var userName = HttpContext.User.Identity.Name;
var appointments = GetAppointments(userName);
return new CalendarResult(appointments, "Appointments.ics");
}
I found this really helpful, but i hit a few problems during the development and i thought i would share some of them to help save other people some time.
I was looking to get data from my web application into the calendar for an android device and i was using discountasp as a hosting service.
The first problem i hit was that the validation did not work when uploaded to the server, stangely enough it was accepting my control panel login for discountasp but not my forms login.
The answer to this was to turn off Basic Authentication in IIS manager. This resolved the issue.
Secondly, the app i used to sync the calendar to the android device was called iCalSync2 - its a nice app and works well. But i found that it only worked properly when the file was delivered as a .ics (duh for some reason i put it as a .ical.. it must have been late) and i also had to choose the webcal option
Lastly i found i had to add webcal:// to the start of my url instead of http://
Also be careful as the code posted above ignores the roles input variable and always passes nothing so you might need to do some role based checks inside your calendar routine or modify the code above to process the roles variable.

Asp.net Mvc custom mechanism to handle unauthorized request

For my website i want following behaviors for secured controller(or action)
if a user makes a normal request redirect to login page (which i have easily able to do)
if request is Ajax type Request.IsAjaxRequest()==true, return status code 401
How can i create a filter for this??
public class MyCustomAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//if ajax request set status code and end Response
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.End();
}
base.HandleUnauthorizedRequest(filterContext);
}
}
Create a filter like above, it will return status code 401 for unauthorized request if request is made thru ajax.
If you are using jQuery you can do as below
jQuery.ajax({
statusCode: {
401: function() {
alert('unauthrized');
},
/*other options*/
});
In addition to the accepted answer, I needed to put this line of code in to prevent FormsAuthentication from redirecting to the login page..
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
I then removed
filterContext.HttpContext.Response.End();
var unauthorizedResult = new JsonResult
{
Data = new ErrorResult() {Success = 0, Error = "Forbidden"},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
// status code
filterContext.HttpContext.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
// return data
filterContext.Result = unauthorizedResult;
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
}
Your problem is not with AJAX request, your problem is returning HTTP 401 Unauthorized response, because you use forms authentication. This response code tells the framework that it should redirect the user-agent to your login page with a HTTP 302 response instead. That's why it was easy to setup the "normal" request redirect - it's done automatically.
To answer your question, I had similar problem and the solution I ended up with was not using forms authentication. I implemented a custom authorization attribute that handles both cases manually instead. I'm not sure if this is the best approach, but it does work. I'm interested in what others think of this solution or what other solutions there are.
Fortunately, you can still use the FormsAuthentication class to handle cookies for you, but you have to delete the forms authentication configuration from your Web.config file. When the user logs in you use FormsAuthentication.SetAuthCookie to, well, set a cookie (you are probably doing this already). Second, in your authorization attribute, you get the cookie from the request and use FormsAuthentication.Decrypt to decrypt it. If it exists and is valid, you set the user in the HttpContext based on this cookie, because forms authentication won't do it for you anymore. If it doesn't you either redirect to the login page or return 401, depending on whether it's an AJAX call or not.
You can use ajaxonly to restrain access to ajax actionresult
You can just return a HttpUnauthorizedResult.
Note: This could cause the MVC framework to return you to the login page.
public ActionResult FailResult()
{
return new HttpUnauthorizedResult();
}
a simple way is to do a check in the SignIn action
public ActionResult SignIn()
{
if (Request.IsAjaxRequest())
{
// you could return a partial view that has this script instead
return Content("<script>window.location = '" + Url.Action("SignIn", "Account") + "'</script>");
}
...
return View();

Redirect user after successful (fake) login in OpenID Offline Provider

Many days ago, I asked this question and I'm set with a working offline OpenID provider (I guess).
What I want is a simple login text box for OpenID identifier which will automatically accept the user and consider the user as logged in, then I want him to be redirected to the main products page (Products => Index).
But, what I didn't know (and didn't find on the internet) is how to go on after the fake authentication process.
I tried to do this:
[HttpPost]
public ActionResult Login(string openid)//openid is the identifier taken from the login textbox
{
var rely = new OpenIdRelyingParty();
var req = rely.CreateRequest(openid);
req.RedirectToProvider();
return RedirectToAction("Index", "Products");
}
First of all, it is not being redirected in the 4th line (instead the login page is refreshed) and second, no authenticated user is really there, I mean when I checked User in Watch window, it is not null but Username is empty and there is no authenticated identity which, I guess, means that there is no cookies set.
P.S:
1. No exceptions are being thrown. But when I try to call FormsAuthentication.RedirectFromLogin() method I get an exception (Server cannot modify cookies after HTTP headers have been set).
2. Index action method is marked with [Authorize] attribute, and so when someone tries to browse Products it is redirected to the login screen, but when he logs in (fake login), shouldn't he be redirected back to Products page?
I tried this also:
[HttpPost]
public ActionResult Login(string openid)
{
var rely = new OpenIdRelyingParty();
return rely.CreateRequest(openid).RedirectingResponse.AsActionResult();
}
But no luck.
What did I miss? and how to make it work as expected? I can depend on myself but I need a decent documentation for OpenID especially for the offline local provider.
Check this example: OpenID and OAuth using DotNetOpenAuth in ASP.NET MVC
public ActionResult OpenId(string openIdUrl)
{
var response = Openid.GetResponse();
if (response == null)
{
// User submitting Identifier
Identifier id;
if (Identifier.TryParse(openIdUrl, out id))
{
try
{
var request = Openid.CreateRequest(openIdUrl);
var fetch = new FetchRequest();
fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
fetch.Attributes.AddRequired(WellKnownAttributes.Name.First);
fetch.Attributes.AddRequired(WellKnownAttributes.Name.Last);
request.AddExtension(fetch);
return request.RedirectingResponse.AsActionResult();
}
catch (ProtocolException ex)
{
_logger.Error("OpenID Exception...", ex);
return RedirectToAction("LoginAction");
}
}
_logger.Info("OpenID Error...invalid url. url='" + openIdUrl + "'");
return RedirectToAction("Login");
}
// OpenID Provider sending assertion response
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
var fetch = response.GetExtension<FetchResponse>();
string firstName = "unknown";
string lastName = "unknown";
string email = "unknown";
if(fetch!=null)
{
firstName = fetch.GetAttributeValue(WellKnownAttributes.Name.First);
lastName = fetch.GetAttributeValue(WellKnownAttributes.Name.Last);
email = fetch.GetAttributeValue(WellKnownAttributes.Contact.Email);
}
// Authentication
FormsAuthentication.SetAuthCookie(userName: email, createPersistentCookie: false);
// Redirection
return RedirectToAction("Index", "Products");
case AuthenticationStatus.Canceled:
_logger.Info("OpenID: Cancelled at provider.");
return RedirectToAction("Login");
case AuthenticationStatus.Failed:
_logger.Error("OpenID Exception...", response.Exception);
return RedirectToAction("Login");
}
return RedirectToAction("Login");
}
Basically, your action method is called twice:
The first time by your form being submitted by the user.
The second time is a call back (redirect) from the OpenID provider.
This time, there will be a value for the response. If response.Status is valid, you can log your user in using the FormsAuthentication class and finally you can redirect him to your main products page.

Custom Authorize attribute HttpContext.Request.RawUrl unexpected results

We're building an application that is a Silverlight client application, but we've created an MVC controller and a few simple views to handle authentication and hosting of the Silverlight control for this application. As part of the security implementation, I've created a custom Authorization filter attribute to handle this, but am getting some unexpected results trying to properly handle redirection after authentication.
For example, our Silverlight application's navigation framework allows users to deep-link to individual pages within the application itself, such as http://myapplicaton.com/#/Product/171. What I want, is to be able to force a user to login to view this page, but then successfully redirect them back to it after successful authentication. My problem is with getting the full, requested URL to redirect the user to from within my custom authorization filter attribute class.
This is what my attribute code looks like:
public class RequiresAuthenticationAttribute : FilterAttribute, IAuthorizationFilter
{
protected bool AuthorizeCore(HttpContextBase httpContext)
{
var cookie = Cookie.Get(SilverlightApplication.Name);
if (SilverlightApplication.RequiresLogin)
{
return
((cookie == null) ||
(cookie["Username"] != httpContext.User.Identity.Name) ||
(cookie["ApplicationName"] != SilverlightApplication.Name) ||
(Convert.ToDateTime(cookie["Timeout"]) >= DateTime.Now));
}
else
return false;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext != null && AuthorizeCore(filterContext.HttpContext))
{
var redirectPath = "~/login{0}";
var returnUrl = filterContext.HttpContext.Request.RawUrl;
if (string.IsNullOrEmpty(returnUrl) || returnUrl == "/")
redirectPath = string.Format(redirectPath, string.Empty);
else
redirectPath = string.Format(redirectPath, string.Format("?returnUrl={0}", returnUrl));
filterContext.Result = new RedirectResult(redirectPath);
}
}
}
So in this case, if I browse directly to http://myapplicaton.com/#/Product/171, in the OnAuthorize method, where I'm grabbing the filterContext.HttpContext.Request.RawUrl property, I would expect it's value to be "/#/Product/171", but it's not. It's always just "/". Does that property not include page level links? Am I missing something?
The # sign in URLs (also called the fragment part of an URL) is only used by browsers to navigate between history and links. Everything following this sign is never sent to the server and there's no way to get it in a server side script.

Resources