How does FormsAuthentication.RedirectFromLoginPage() work? - asp.net-mvc

It doesn't return a view. In fact, the Action still needs to return a view after calling this ... so what's going on?

If you want to use the FormsAuthentication system, you'll want to switch to this line (which implicitly uses the returnUrl parameter).
return Redirect(FormsAuthentication.GetRedirectUrl(model.UserName, true));
You will get the URL that FormsAuthentication.RedirectFromLoginPage would have used, but you will explicitly bail from the action method with a RedirectResult instead.
Note
If you go this route, you'll want to put a defaultUrl parameter in the forms auth web.config line in case someone goes directly to your login page (or they pass in a redirectUrl that doesn't pass FormsAuthentication's security restrictions). Without overriding the default, bad URLs will be redirected to ~/default.aspx. In most MVC apps, that will likely 404.
<forms loginUrl="~/Account/LogOn" defaultUrl="~/" timeout="2880">
Alternative
If you spin up a new MVC 3 sample "Internet Application", you will find a LogOn action method that handles a returnUrl similar to what FormsAuthentication.RedirectFromLoginPage does internally.
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\")) {
return Redirect(returnUrl);
}
else {
return RedirectToAction("Index", "Home");
}

It's exactly what it says - a redirect. This is a response code sent to the browser to ask it to request another URL. That's the point at which a view is requested in MVC, or a web page in straight ASP.NET.

Related

User.Isauthenticated works randomly in ASP mvc

I am trying to make sure that my users log in as a elementryUser .So in Login controller i check the username and password ,if the authentication be true the user can enter the page the code to handle this is given below :(this part of code is login action )
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
System.Threading.Thread.Sleep(10000);
if (User.IsInRole("ElementryUser"))
{
int UserId = objuserrepository.FindBy(i => i.Email == User.Identity.Name).First().Id;
if (firstIdeaRepository.FindBy(i => i.UserId == UserId).Count() > 0)
{
return RedirectToAction("Index", "FirstIdea");
}
}
else
{
return RedirectToAction("Index", "Dashboard");
}
As you can see if the username and password be true the cookie is initialized,in this line if (User.IsInRole("ElementryUser")) when i want to check my user permission but it doesn't work and it doesn't execute if statement .so i trace the code and i found that the User.Isauthenticated returns false !!!!why?what is the problem?So i put and thread between these two line because i thought maybe the thread could solve the problem.But it doens't workUser.Isauthenticated returns false and sometimes returns true and when it returns true my if statement works .!!
Best regards
I am answer here general, because I can not do otherwise to your question. I can only give you some guidelines to look, and maybe move forward.
The authentication is connected with one cookie, as you already know.
So if the cookie is not readed then user is not authenticated, beside the fact that can be readed and not authenticate for other reasons.
When a cookie that you have set one one page, can not be readed you can check this reasons:
When you move from https to http pages and the cookie is set only for secure pages.
When you move from example.com to www.example.com to whatevet.example.com
When you set a cookie, but you make a redirect before the cookie have been transmitted to the client.
On web.config you can set up the 1 and 2 on this line.
<authentication mode="Forms">
<forms name=".auth"
path="/"
requireSSL="false"
cookieless="UseCookies"
domain="example.com"
enableCrossAppRedirects="false" />
</authentication>
Set the path, the requireSSL and the domain, without subdomains to set cookie be visible everywhere on your site.
Now if you left the requireSSL="false" the cookie can be possible read by the middle men and login to your site if stolen. Related : Can some hacker steal the cookie from a user and login with that name on a web site?
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); only sets the cookie, it doesn't set IPrincipal in your current request context ("User" is a shortcut to the current requests IPrincipal).
On the next request the user makes to the server, the browser will send the cookie and the FormsAuthentication module will then read that cookie and set IPrincipal on the current request context.
In other words, do something like
public ActionResult Login()
{
... stuff ...
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToAction("someaction");
}
public ActionResult SomeAction()
{
if (User.IsInRole("ElementryUser"))
{
int UserId = objuserrepository.FindBy(i => i.Email == User.Identity.Name).First().Id;
if (firstIdeaRepository.FindBy(i => i.UserId == UserId).Count() > 0)
{
return RedirectToAction("Index", "FirstIdea");
}
}
else
{
return RedirectToAction("Index", "Dashboard");
}
}
This is a problem I have seen in my own apps before. You do end up with an extra roundtrip, but it's only at login so you should be fine.
I suspect the reason you see it sometimes working would have been because you had an authentication cookie already set from your previous test, i.e. you were already logged in before you called FormsAuthentication.SetAuthCookie.
No need of Thread.Sleep();
You can follow this article. You have to use custom authorization filter:
http://www.dotnet-tricks.com/Tutorial/mvc/G54G220114-Custom-Authentication-and-Authorization-in-ASP.NET-MVC.html

Why once SSL is enabled with [RequireHttps] at action level, it remains enabled forever?

We want to use https only when strictly required. Why after calling an action like below it remains enabled forever?
[RequireHttps]
public ActionResult LogIn()
{
if(Request.IsAuthenticated)
return RedirectToAction("Index", "Account");
return View();
}
What can we do to disable it when not needed?
Thanks.
The [RequireHttps] attribute can be used on a controller type or action method to say "this can be accessed only via SSL." Non-SSL requests to the controller or action will be redirected to the SSL version (if an HTTP GET) or rejected (if an HTTP POST). You can override the RequireHttpsAttribute and change this behavior if you wish. There's no [RequireHttp] attribute built-in that does the opposite, but you could easily make your own if you desired.
There are also overloads of Html.ActionLink() which take a protocol parameter; you can explicitly specify "http" or "https" as the protocol. Here's the MSDN documentation on one such overload. If you don't specify a protocol or if you call an overload which doesn't have a protocol parameter, it's assumed you wanted the link to have the same protocol as the current request.
The reason we don’t have a [RequireHttp] attribute in MVC is that there’s not really much benefit to it. It’s not as interesting as [RequireHttps], and it encourages users to do the wrong thing. For example, many web sites log in via SSL and redirect back to HTTP after you’re logged in, which is absolutely the wrong thing to do. Your login cookie is just as secret as your username + password, and now you’re sending it in cleartext across the wire. Besides, you’ve already taken the time to perform the handshake and secure the channel (which is the bulk of what makes HTTPS slower than HTTP) before the MVC pipeline is run, so [RequireHttp] won’t make the current request or future requests much faster.
If you're hosting utube, change your embedding to use HTTPS rather than HTTP
If you drop down to HTTP from HTTPS without correctly signing out (see http://msdn.microsoft.com/en-us/library/system.web.security.formsauthentication.signout.aspx ) your username + password is wide open. It's not enough to call SignOut.
I use this action filter that redirects back to http when the https action is completed:
using System.Web.Mvc;
using System;
public class ExitHttpsIfNotRequiredAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// abort if it's not a secure connection
if (!filterContext.HttpContext.Request.IsSecureConnection) return;
// abort if a [RequireHttps] attribute is applied to controller or action
if (filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), true).Length > 0) return;
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), true).Length > 0) return;
// abort if a [RetainHttps] attribute is applied to controller or action
if (filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(RetainHttpsAttribute), true).Length > 0) return;
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(RetainHttpsAttribute), true).Length > 0) return;
// abort if it's not a GET request - we don't want to be redirecting on a form post
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) return;
// redirect to HTTP
string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}

MVC - Identify Page Form Authentication time out

we are developing MVC3 application such that most of our action methods are called via ajax calls and return partialviews. we come across a situation where we need to identify if the action method is called from Form Authentication time out.
public ActionResult LogOn()
{
// I want to return View("LogOn"); if the call is coming from
// Form Authentication time out
return PartialView(Model);
}
here is my web.config looks like:
<authentication mode="Forms">
<forms loginUrl="~/Home/LogOn" timeout="20" />
</authentication>
Appreciate your input.
Your action will never be hit if the authentication cookie has timed out. The forms authentication module directly redirects to the logon page. One possibility for you to detect this happening from client scripting is to set a custom HTTP header in the controller action serving this logon page:
public ActionResult LogOn()
{
var model = ...
Response.AppendHeader("X-LOGON", "true");
return View(model);
}
and then when performing your AJAX request you could use the getResponseHeader method on the XHR object in order to verify if the X-LOGON header was set meaning that the server redirected to the logon page. In this case in your success AJAX handler instead of simply injecting the server response into the DOM or relying on the returned JSON you could show some alert message informing the user that his authentication session has timed out and he needs to login again. Another possibility is to automatically redirect him to the logon page using the window.location.href method:
$.ajax({
url: '/home/some_protected_action',
success: function (data, textStatus, XMLHttpRequest) {
if (XMLHttpRequest.getResponseHeader('X-LOGON') === 'true') {
// the LogOn page was displayed as a result of this request
// probably timeout => act accordingly
}
}
});
There is no way from the server to distinguish between the user loading the page normally versus performing a page refresh.
There are ways to tell the difference between a regular request and an AJAX request, but it doesn't sound like that's what you're asking for.
There is no easy way but if you apply Post-Redirect-Get, I am not sure you will have that problem.

ASP.NET MVC: Redirecting back to page when no parameter is provided in Firefox 3

I'm trying to do exactly the same thing as detailed in this question:
ASP.NET MVC: Redirecting back to page when no parameter is given to URL
Here's my code:
public ActionResult Details(long? id)
{
if (!id.HasValue)
return RedirectToAction("Index");
Models.Track track = Models.Track.GetTrack(id.Value);
if (track == null)
return View("NotFound");
else
return View("Details", track);
}
However, when I call RedirectToAction("Index") and I'm viewing the page in Firefox 3, the page hangs. It redirects fine in IE7.
Are there any known issues with RedirectToAction in Firefox 3?
Try this. Open Firefox. Type "about:config" in the address bar. Hit enter. Accept the warning. Then look for:
network.dns.disableIPv6
set it to true by double-clicking the line. Try your web app now. Does that work?
I don't know how your urls are configured but maybe you are in a recursive loop? That you are continously redirecting to the same page?

Why does AuthorizeAttribute redirect to the login page for authentication and authorization failures?

In ASP.NET MVC, you can mark up a controller method with AuthorizeAttribute, like this:
[Authorize(Roles = "CanDeleteTags")]
public void Delete(string tagName)
{
// ...
}
This means that, if the currently logged-in user is not in the "CanDeleteTags" role, the controller method will never be called.
Unfortunately, for failures, AuthorizeAttribute returns HttpUnauthorizedResult, which always returns HTTP status code 401. This causes a redirection to the login page.
If the user isn't logged in, this makes perfect sense. However, if the user is already logged in, but isn't in the required role, it's confusing to send them back to the login page.
It seems that AuthorizeAttribute conflates authentication and authorization.
This seems like a bit of an oversight in ASP.NET MVC, or am I missing something?
I've had to cook up a DemandRoleAttribute that separates the two. When the user isn't authenticated, it returns HTTP 401, sending them to the login page. When the user is logged in, but isn't in the required role, it creates a NotAuthorizedResult instead. Currently this redirects to an error page.
Surely I didn't have to do this?
When it was first developed, System.Web.Mvc.AuthorizeAttribute was doing the right thing -
older revisions of the HTTP specification used status code 401 for both "unauthorized" and "unauthenticated".
From the original specification:
If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials.
In fact, you can see the confusion right there - it uses the word "authorization" when it means "authentication". In everyday practice, however, it makes more sense to return a 403 Forbidden when the user is authenticated but not authorized. It's unlikely the user would have a second set of credentials that would give them access - bad user experience all around.
Consider most operating systems - when you attempt to read a file you don't have permission to access, you aren't shown a login screen!
Thankfully, the HTTP specifications were updated (June 2014) to remove the ambiguity.
From "Hyper Text Transport Protocol (HTTP/1.1): Authentication" (RFC 7235):
The 401 (Unauthorized) status code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource.
From "Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content" (RFC 7231):
The 403 (Forbidden) status code indicates that the server understood the request but refuses to authorize it.
Interestingly enough, at the time ASP.NET MVC 1 was released the behavior of AuthorizeAttribute was correct. Now, the behavior is incorrect - the HTTP/1.1 specification was fixed.
Rather than attempt to change ASP.NET's login page redirects, it's easier just to fix the problem at the source. You can create a new attribute with the same name (AuthorizeAttribute) in your website's default namespace (this is very important) then the compiler will automatically pick it up instead of MVC's standard one. Of course, you could always give the attribute a new name if you'd rather take that approach.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
Add this to your Login Page_Load function:
// User was redirected here because of authorization section
if (User.Identity != null && User.Identity.IsAuthenticated)
Response.Redirect("Unauthorized.aspx");
When the user is redirected there but is already logged in, it shows the unauthorized page. If they are not logged in, it falls through and shows the login page.
I always thought this did make sense. If you're logged in and you try to hit a page that requires a role you don't have, you get forwarded to the login screen asking you to log in with a user who does have the role.
You might add logic to the login page that checks to see if the user is already authenticated. You could add a friendly message that explains why they've been bumbed back there again.
Unfortunately, you're dealing with the default behavior of ASP.NET forms authentication. There is a workaround (I haven't tried it) discussed here:
http://www.codeproject.com/KB/aspnet/Custon401Page.aspx
(It's not specific to MVC)
I think in most cases the best solution is to restrict access to unauthorized resources prior to the user trying to get there. By removing/graying out the link or button that might take them to this unauthorized page.
It probably would be nice to have an additional parameter on the attribute to specify where to redirect an unauthorized user. But in the meantime, I look at the AuthorizeAttribute as a safety net.
Try this in your in the Application_EndRequest handler of your Global.ascx file
if (HttpContext.Current.Response.Status.StartsWith("302") && HttpContext.Current.Request.Url.ToString().Contains("/<restricted_path>/"))
{
HttpContext.Current.Response.ClearContent();
Response.Redirect("~/AccessDenied.aspx");
}
If your using aspnetcore 2.0, use this:
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Core
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeApiAttribute : Microsoft.AspNetCore.Authorization.AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
context.Result = new UnauthorizedResult();
return;
}
}
}
}
In my case the problem was "HTTP specification used status code 401 for both "unauthorized" and "unauthenticated"". As ShadowChaser said.
This solution works for me:
if (User != null && User.Identity.IsAuthenticated && Response.StatusCode == 401)
{
//Do whatever
//In my case redirect to error page
Response.RedirectToRoute("Default", new { controller = "Home", action = "ErrorUnauthorized" });
}

Resources