User.Isauthenticated works randomly in ASP mvc - asp.net-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

Related

MVC give authorization on login to access actions

I've been running in circles trying to find an answer to this, and I can't seem to make any progress.
All I want to do, is check for a correct username and password combo, then GIVE the user authorization to access MVC actions decorated with the [Authorize] tag.
public ActionResult DoLogin(PageInitModel model)
{
EF_db db = new EF_db();
string saltPassword = getSaltedPasswordEncryption(model.UserName, model.Password);
var user = (from s in db.Users
where s.Username == model.UserName
&& s.Password == saltPassword
select s).FirstOrDefault();
if(user == null)
{
model.LoginFail = true;
return View("Login", model);
}
else
{
//
// give the user some magical token to access [Authorize] actions here
//
return RedirectToAction("Index", "Menu");
}
}
Above is the login action (called from a basic form), and below would be one of the actions I would like to restrict access to:
public class MenuController : Controller
{
[Authorize]
public ActionResult Index()
{
var pageInitModel = new PageInitModel();
return View("Menu",pageInitModel);
}
}
I would like to keep track of the users myself (in my own tables), because there are many additional attributes I would like to track. I'm not sure if I need to write a custom AuthorizeAttribute, or what, but I can't seem to make any headway.
Any help pointing me in the right direction is greatly appreciated.
Thanks!
You should look into ASP.NET Identity. Note this was introduced in ASP.NET 5 (?) and replaces some older frameworks Microsoft had like Basic Membership etc.
Honestly you really don't want to roll your own. ASP.NET Identity does exactly what you describe right out of the box. Keep in mind there are two distinct concepts Authentication and Authorization.
Authentication is verifying the user is who he says he is.
Authorization is restricting access to only users who are "allowed".
There are many ways to structure Authorization but I assume Role based Authorization will meet your need. You will define multiple roles, say User, Admin, Moderator, Admin, etc.
You then restrict access to actions based on the role. You can even make roles overlap and allow a single user to have multiple roles. The end result is that once a user logs in their role determines what they can do via the authorize tags.
[Authorize(Roles="Admin")]
If the user is not logged in, they will be redirected to the login form to AUTHENTICATE ("Um I don't know who you are". Once authenticated if they are not authorized they will still be restricted from this action ("Oh I know who you are but you are not allowed to do this".
To round it out you can also have anonymous actions which mean no authentication is required and Authorize actions which are not limited to a specific role (any authenticated user is allowed but not unauthenticated users).
The fact that even with a basic [Authorize] you are having issues leads be to believe there is some configuration problems in even the Authentication. I recommend going through a tutorial building an example app like this one:
http://blogs.msdn.com/b/webdev/archive/2013/10/20/building-a-simple-todo-application-with-asp-net-identity-and-associating-users-with-todoes.aspx
As I understood you have your users information and you may want to use the authentication by custom code. But to actually login the user you must create a session of the user (Cookie session) for further authentication of requests.
You have already verified the username and password. now you've to create a session cookie using form authentication. Here's answer to your first problem:
// give the user some magical token to access [Authorize] actions here
// User name and password matches
if (userValid)
{
FormsAuthentication.SetAuthCookie(username, false);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
The Authorize attribute will then check if the request coming for the action is authenticated. This attribute internally checks if a session cookie is valid and authenticated.
Also this attribute not only checks for authentication but also can verify authorization. So you can use custom roles as well with Authorize attribute to restrict users to accessing specific views/actions.
It was pointed out to me that I needed to look into cookies or existing membership providers, so I looked around in FormsAuthenticationTickets and rolled my own solution that I would love feedback on.
I ended up creating a new ticket when the login was successful with the FormsAuthenticationTicket class (see below).
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
user.Username,
DateTime.Now,
DateTime.Now.AddHours(2),
false,
userData,
FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
Then I wrote a custom ActionFilter, so I could make my own method decorators (or whatever they're called).
namespace MyApp.Filters
{
public class CustomLoginFilter : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
// Retrieves the cookie that contains your custom FormsAuthenticationTicket.
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
// There's actually a cookie
// Decrypts the FormsAuthenticationTicket that is held in the cookie's .Value property.
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null && user != null && data == authTicket.UserData)
{
// Everything looks good
this.OnActionExecuting(filterContext);
}
else
{
//log bounceback - someone screwed with the cookie
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Login" },
{ "action", "Index" }
});
}
}
else
{
//log bounceback - not logged in
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Login" },
{ "action", "Index" }
});
}
}
}
}
I'll likely play with it more, but it seems to work fine for now.
Thanks for the help.

Why isn't my authentication cookie being set in MVC 4?

I've got an MVC4 project that I'm working on. When a user's login credentials are valid, I call FormsAuthentication.SetAuthCookie() to indicate that the user is logged in. (I have it wrapped in a class so I can mock the Interface for my unit tests.)
namespace FlashMercy.Shared.Security
{
using System;
using System.Web.Security;
public class Auth : IAuth
{
public void SetAuthCookie(string userId, bool remember)
{
FormsAuthentication.SetAuthCookie(userId, remember);
}
public void Signout()
{
FormsAuthentication.SignOut();
}
}
}
In the debugger, I can confirm that the .SetAuthCookie(userId, remember) line is executing, and userId is populated.
Then, I have a custom authorize attribute to check that the user is logged in:
namespace FlashMercy.Shared.Security
{
using System.Web.Mvc;
public class FlashMercyAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectResult("/");
}
}
}
}
When I debug the application, the filterContext.HttpContext.User.Identity.IsAuthenticated is false even after I've supposedly set the auth cookie. Also, filterContext.HttpContext.User.Identity.Name is empty. I'm not sure what I'm missing here.
Update
If you care to look at the whole source, it's available on GitHub: https://github.com/quakkels/flashmercy.
Problem with your code is that you are using FormsAuthentication, but you didn't add it to web.config. Your web.config should have such section:
<system.web>
<authentication mode="Forms"></authentication>
...
</system.web>
Based on this Mode Asp.Net understand what authentication mode it should use, e.g. Forms, Windows, etc. And without settings it to Forms value - FormsAuthenticationModule just ignores .ASPXAUTH cookie from the request.
PS. I've downloaded your code, and with correct authentication section in web.config it works fine and updates HttpContext.User.Identity.IsAuthenticated to true.
The problem is that you only set the authentication cookie but do not have anything that load it.
It's forms authentication that uses that cookie. So you either have to activate forms authentication or you'll have to load it yourself.
filterContext.HttpContext.User.Identity.IsAuthenticated is false even after I've supposedly set the auth cookie.
This will always be the case if you do not redirect after SetAuthCookie(). The ASP.Net pipeline is in charge of authorizing the user (most of the time before we write code) in the AuthenticateRequest. Setting a Cookie does not update the current User.Identity, this requires code that has already been executed. Just make sure anytime you SetAuthCookie() you immediately redirect (server side is fine) to another URL (probably should anyway, its a good way to seperate logging in a user, and what they should do next SRP).

MVC 4 SimpleMembership - Why WebSecurity.CurrentUserId -1 after login

I am trying to set a cookie upon login and having issues with getting the current user id after login. In the below, intUserId is -1 and WebSecurity.IsAuthenticated is false. Is this not the correct place to put this code? After this, it redirects to the home page...so not sure why this is not the correct place.
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
//TODO: set current company and facility based on those this user has access to
int intUserId = WebSecurity.GetUserId(User.Identity.Name);
int intUserId2 = WebSecurity.GetUserId(model.UserName);
UserSessionPreferences.CurrentCompanyId = 1;
UserSessionPreferences.CurrentFacilityId = 1;
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
Login only sets the Forms Authentication cookie.
The way asp.net authentication works is that it must read the cookie to set the authenticate the request, but since the cookie did not exist when the Login page was started, the framework doesn't know anything about the user.
Reload the page, and you will find the information is available.
FYI, this is nothing new with SimpleMembership or WebSecurity, this is the way Forms Authentication has always worked.

How does FormsAuthentication.RedirectFromLoginPage() work?

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.

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