Currently, I am working on a web application, based on the roles we have to display the controls in the UI. Roles are stored in the DB, whenever the user logs in, by fetching the user Id, I will hit the DB and get the user role and store it in the cookie. So, for the next request, I will fetch the user role from the User.IsInRole() and proceed with the logic same will happens in the view. This entire thing is working fine with the single server but when it comes to load balancer, this behaving weirdly and intermittently it's giving issue, as User.IsInRole() is returning false sometime.
The code in my controller:
public ActionResult IsValidUser(string userName)
{
try
{
if (HttpContext!=null&& HttpContext.Request.Headers["username"] != null)
{
userName = HttpContext.Request.Headers["username"];
}
//Get the roles by sending the user name
userRole = _processor.GetUserRole(userName);
UserViewModel user = new UserViewModel()
{
UserName = userName,
Role = userRole
};
if (!string.IsNullOrEmpty(user.Role))
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, user.UserName, DateTime.Now, DateTime.Now.AddMinutes(2880), true, user.Role, FormsAuthentication.FormsCookiePath);
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
if (ticket.IsPersistent)
{
cookie.Expires = ticket.Expiration;
}
if(Response!=null)
Response.Cookies.Add(cookie);
if (!string.IsNullOrEmpty(Request.Form["ReturnUrl"]))
{
return View("Error");
}
else
{
return RedirectToAction("Index");
}
}
else
{
return View("Error")
}
}
else
return RedirectToAction("Search");
}
catch (Exception ex)
{
return View("Error);
}
}
The code in Global.asax.cs
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
string userData = ticket.UserData;
string[] roles = userData.Split(',');
HttpContext.Current.User = new GenericPrincipal(id, roles);
}
}
}
}
the code in my controller to do the logic:
public FileContentResult ViewAttachment(string attachmentName, int attachmentId)
{
if (ConfigurationManager.AppSettings["Environment"] != Constants.ProductionEnvironment ||
(User.IsInRole(Constants.Administrator) || User.IsInRole(Constants.Contributor) || User.IsInRole(Constants.Member)))
{
//Logic here
return File(bytes, mimeType);
}
else
{
_logger.WriteInformation("Not authorized");
return File(bytes, mimeType);
}
}
I am not sure what mistake is there but this is not working in load balancer sometimes it is showing "User is not authorized" but in actual user is authorized. Is it because of cookies or load balancer? Any help would be appreciated. Thanks in advance.
Related
I am new to ASP.NET MVC and am trying to create a web app.
The problem I have is that in the controller class I need to get the UserID of the current user, but I am very confused about how one would do that.
Also, it seems that the user is not authenticated after logging in, because if I use the [Authorize] annotation it throws an HTTP Error 401.0 - Unauthorized error.
This is my Authentication.cs class:
public static class Authentication
{
public static bool CreateNewTicket(User user, bool rememberMe)
{
try
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
user.Email,
DateTime.Now,
DateTime.Now.AddDays(5),
rememberMe,
user.ID.ToString(),
FormsAuthentication.FormsCookiePath
);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.HttpOnly = true;
if (!HttpContext.Current.Request.IsLocal)
cookie.Secure = true;
HttpContext.Current.Response.Cookies.Add(cookie);
return true;
}
catch
{
return false;
}
}
public static bool AuthUser(string Email, string Password)
{
using (var db = new AntContext())
{
string password = Password;
string email = Email;
string hashedPW = GetHash(password);
bool userValid = db.Users.Any(user => user.Email == email && user.Password == hashedPW);
if (userValid)
{
var actUser = db.Users.FirstOrDefault(u => u.Email == Email && u.Password == hashedPW);
if (!actUser.IsLocked)
{
if (CreateNewTicket(actUser, false))
{
return true;
}
else
{
return false;
}
}
else if (actUser.IsLocked)
{
}
}
return false;
}
}
The actual problem happens when I try to store data in a database.
[HttpPost]
public ActionResult Q_FirstPage(ViewModels.Q1_Answer_VM vm)
{
vm.Qst = new Models.Questionnaire();
vm.Qst.NumericAnswers = new List<Models.NumericAnswer>();
vm.Qst.TextAnswers = new List<Models.TextAnswer>();
vm.Qst.IsComplete = false;
vm.Qst.StartedOn = DateTime.Now;
vm.Qst.NumericAnswers.Add(vm.Breite);
vm.Qst.NumericAnswers.Add(vm.Tiefe);
vm.Qst.NumericAnswers.Add(vm.Hoehe);
vm.Qst.TextAnswers.Add(vm.Sonstiges);
//vm.qst.User_ID = 22; if I set the User ID manually, it works
db.Questionnaires.Add(vm.Qst);
db.SaveChanges();
return View();
}
The Viewmodel works fine and returns the data input, but the UserID is null. The data table "Questionnaire" uses the UserID as a foreign key, which makes it throw an error when it comes to the savedata() part because I guess it expects the correct UserID. So I guess I need to get the current UserID, pass it to the instantiated object which is then passed to the data context and then saved into the database.
Unfortunately, I find it very hard to find complete information about how user authentication works in ASP.NET.
If you need more information, please let me know.
This is my Login method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(Login_VM login_vm)
{
if (!ModelState.IsValid)
{
return View(login_vm);
}
if (Authentication.AuthUser(login_vm.Email, login_vm.Password) == true && (login_vm.Email != null || login_vm.Password != null))
{
Classes.Authentication.CreateNewTicket(login_vm.usr, true);
return RedirectToAction("Login");
}
else
return View("~/Views/Home/Index.cshtml");
}
And this is my registration method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddUser(User model)
// add new User to db
{
if (ModelState.IsValid)
{
User usr = new Models.User();
usr = model;
model.Password = Authentication.GetHash(model.Password);
db.Users.Add(model);
db.SaveChanges();
}
return View();
}
Solved the problem by following this link: howto get the user id from a FormsAuthentication page in asp.net MVC? posted by https://stackoverflow.com/users/2516718/derloopkat
The System.Web.HttpContext.Current.User.Identity.Name Function returns the "name" attribute in the Authentication Ticket, which in my case was the email address. I then got the User ID by having a query to the Users database.
db.Users.Where(x => x.Email == System.Web.HttpContext.Current.User.Identity.Name).FirstOrDefault().ID;
Thanks for everybody's help.
Update in 2020: The query can be simplified to:
db.Users.FirstOrDefault(x => x.Email == System.Web.HttpContext.Current.User.Identity.Name).ID;
There are two simple ways to get current user in MVC 5.
If you are inside the controller class,the current user can be fetched as follows,
string userId = User.Identity.GetUserId();
Do not forget to add namespace:
using Microsoft.AspNet.Identity;
Other scenario could be that you are not inside the controller class and want to fetch the user information. You can fetch that using HttpContext class.
HttpContext.Current.User.Identity.GetUserId();
I want to do custom authentication because we have many controllers and it makes sense to create global filter that applies for all controllers and their actions with exception of login page.
In Global.asax.cs I added next global filter:
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e) // Code that runs on application startup
{
... // only showing important part
GlobalFilters.Filters.Add(new Filters.AuthenticationUserActionFilter());
...
}
File AuthenticationUserActionFilter.cs:
public class AuthorizeUserActionFilter : System.Web.Mvc.Filters.IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousActionFilter), inherit: true) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousActionFilter), inherit: true);
if (skipAuthorization) // anonymous filter attribute in front of controller or controller method
return;
// does this always read value from ASPXAUTH cookie ?
bool userAuthenticated = filterContext.HttpContext.User.Identity.IsAuthenticated;
if (!userAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary() { { "controller", "Account" }, { "action", "Login" } });
return;
}
if( HttpContext.Current.User as Contracts.IUser == null )
{
// check if IUser is stored in session otherwise retrieve from db
// System.Web.HttpContext.Current.User is reseted on every request.
// Is it ok to set it from Session on every request? Is there any other better approach?
if (HttpContext.Current.Session["User"] != null && HttpContext.Current.Session["User"] as Contracts.IUser != null)
{
HttpContext.Current.User = HttpContext.Current.Session["User"] as Contracts.IUser;
}
else
{
var service = new LoginService();
Contracts.ISer user = service.GetUser(filterContext.HttpContext.User.Identity.Name);
HttpContext.Current.Session["User"] = user;
HttpContext.Current.User = user;
}
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext) {}
}
My login code is like this (in AccountController.cs):
[Filters.AllowAnonymousActionFilter]
[HttpPost]
public JsonResult Login(string username, string password, bool rememberMe = false)
{
LoginService service = new LoginService();
Contracts.IUser user = service .Login(username, password);
System.Web.HttpContext.Current.Session["User"] = value;
System.Web.HttpContext.Current.User = value;
// set cookie i.e. ASPX_AUTH, if remember me, make cookie persistent, even if user closed browser
if (System.Web.Security.FormsAuthentication.IsEnabled)
System.Web.Security.FormsAuthentication.SetAuthCookie(username, rememberMe);
return new SuccessResponseMessage().AsJsonNetResult();
}
Contracts.IUser interface:
public interface IUser : IPrincipal
{
Contracts.IUserInfo UserInfo { get; }
Contracts.ICultureInfo UserCulture { get; }
}
My question is this:
System.Web.HttpContext.Current.User is reseted on every request. Is it ok to set HttpContext.Current.User with Session value on every request? Is there any other better approach? What is best practise? Also Microsoft seems to have multiple ways of dealing with this problem (googled a lot of articles on this, also on stackoverflow Custom Authorization in Asp.net WebApi - what a mess?). There is a lot of confusion about this, although they developed a new Authorization in asp.net core.
One possible approach is to serialize the user as part of the UserData portion of the ASPXAUTH cookie. This way you don't need to fetch it from the database on each request and you don't need to use Sessions (because if you use sessions in a web-farm you will have to persist this session somewhere like in a database, so you will be round-tripping to the db anyway):
[Filters.AllowAnonymousActionFilter]
[HttpPost]
public JsonResult Login(string username, string password, bool rememberMe = false)
{
LoginService service = new LoginService();
Contracts.IUser user = service.Login(username, password);
string userData = Serialize(user); // Up to you to write this Serialize method
var ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddHours(24), rememberMe, userData);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket));
return new SuccessResponseMessage().AsJsonNetResult();
}
And then in your custom authorization filter you could decrypt the ticket and authenticate the user:
public void OnAuthentication(AuthenticationContext filterContext)
{
... your stuff about the AllowAnonymousActionFilter comes here
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null)
{
// Unauthorized
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary() { { "controller", "Account" }, { "action", "Login" } });
return;
}
// Get the forms authentication ticket.
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
Contracts.ISer user = Deserialize(authTicket.UserData); // Up to you to write this Deserialize method -> it should be the reverse of what you did in your Login action
filterContext.HttpContext.User = user;
}
I am using MVC form base custom authentication using SQL database. I've Column with CustomerRole name.
I am checking Authorization as per following:
TestController.CS
[Authorize]
public ActionResult Index()
{
return View();
}
[Authorize(Roles="admin")]
public ActionResult AdminPage()
{
return View();
}
AccountController.cs
[HttpPost]
public ActionResult Login(UserModel model, string returnUrl)
{
// Lets first check if the Model is valid or not
if (ModelState.IsValid)
{
using (userDbEntities entities = new userDbEntities())
{
string username = model.username;
string password = model.password;
// Now if our password was enctypted or hashed we would have done the
// same operation on the user entered password here, But for now
// since the password is in plain text lets just authenticate directly
bool userValid = entities.Tbl_UserMast.Any(user => user.UserName == username && user.UserPassword == password);
// User found in the database
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");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
So when I go AdminPage Action. It shows me I am not Authorized.
If I change my column name as Roles, it is working. But I am not allowed to change column name. Is there any other alternative, where I can use Authorization with same column name
You should Try Custom Authentication Filer
Try this:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//let us take out the username now
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
using (userDbEntities entities = new userDbEntities())
{
var user = entities.Users.SingleOrDefault(u => u.username == UserName);
roles = user.UserRole;
}
//let us extract the roles from our own custom cookie
//Let us set the Pricipal with our user specific details
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
}
catch (Exception)
{
//somehting went wrong
}
}
}
}
I have been assigned to customize this MVC application. It has Forms Authentication, and I was instructed to customize it so that the "Login" Process is eliminated and all users can access the app. The application was built so that all operations are done around the logged in User.
Since I needed an authenticated user, I took the code which was responsible to log in and authenticate the user and put it in Application_AcquireRequestState
This is my Login Code:
public Action Login(string username, string password)
{
bool loginResult = false;
try
{
// If validated, it sets the LoginStatus to true and populates the Variables for security
bool validate = this.memberShip.ValidateUser(username,password);
if (validate)
{
// CxIdentity extends and implements IIdentity
var identity = new CxIdentity(memberShip.LoginStatus.SecurityUser.Name,
memberShip.LoginStatus.SecurityUser.Id,
"",
true);
this.Session[this.memberShip.PrincipalSessionKey] = new CxPrincipal(identity, memberShip.LoginStatus.SecurityUser.RoleId);
this.Session[SessionName.CurrentUser] = memberShip.LoginStatus.SecurityUser.UserNm;
FormsAuthentication.SetAuthCookie(username, false, "MyApp/Cookies");
loginResult = true;
}
}
catch (Exception ex)
{
Logger.Error("Login failed: ", ex);
}
if(loginResult)
return Redirect("~/Home");
else
return Redirect("~/Login");
}
And the following is the code of Application_AcquireRequestState, prior to when I tried to merge it
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Handler is IRequiresSessionState)
{
if (Request.IsAuthenticated)
{
if (HttpContext.Current.Session[this.MemberShipProvider.PrincipalSessionKey] != null)
{
CxPrincipal principal;
try
{
principal = (CxPrincipal)HttpContext.Current.Session[this.MemberShipProvider.PrincipalSessionKey];
}
catch
{
principal = null;
}
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = principal;
}
else
{
if (User.Identity.IsAuthenticated && User.Identity is FormsIdentity)
{
FormsAuthentication.SignOut();
Response.Redirect("~/Login");
}
}
}
}
}
The application was written is such a way that if the User isn't Authenticated the applciation will go drectly to Login page. So I changed the Application_AcquireRequestState as follows:
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Handler is IRequiresSessionState)
{
var sessioncheck = this.Session[SessionName.CurrentUser];
var obj = this.Session[this.MemberShipProvider.PrincipalSessionKey];
if (obj != null)
{
HttpContext.Current.User = (CxPrincipal)obj;
Thread.CurrentPrincipal = (CxPrincipal)obj;
}
else
{
this.MemberShipProvider.ValidateUser("appadmin", "#dmin##!8854");
var identity = new CxIdentity("appadmin", 1, "", true);
CxPrincipal principalLogin = new CxPrincipal(identity, 1);
this.Session[this.MemberShipProvider.PrincipalSessionKey] = principalLogin;
this.Session[SessionName.CurrentUser] = "appadmin";
FormsAuthentication.SetAuthCookie("appadmin", false, "MyApp/Cookies");
}
if (Request.IsAuthenticated)
{
if (this.Session[this.MemberShipProvider.PrincipalSessionKey] != null)
{
CxPrincipal principal;
try
{
principal = (CxPrincipal)this.Session[this.MemberShipProvider.PrincipalSessionKey];
}
catch
{
principal = null;
}
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = principal;
}
}
}
}
The problem is that everytime I check obj, it is always null. The Request.IsAuthenticated also gets true and while the application is within the acquire state the session does get set (that is why I get values in HttpContext.Current.User and Thread.CurrentPrincipal.
But when I redirect to another page, the session is gone.
Can anyone guide me through this.
This is the code I used for membership in Global.asax
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//let us take out the username now
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
IUserService _userService= new UserService();
UserViewModel user = _userService.SelectUserByUserName(username).UserList.FirstOrDefault();
roles = user.role;
//let us extract the roles from our own custom cookie
//Let us set the Pricipal with our user specific details
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(','));
}
catch (Exception)
{
//somehting went wrong
}
}
}
}
I'm trying to redirect the user for different view if his Role is "Manager",this is what I tried to get the user roles in the controller but It returns an empty list :
[Authorize(Roles = "admin, manager")]
public ActionResult Index()
{
string[] rolesArray;
rolesArray = Roles.GetAllRoles();// returns an empty array
foreach(var item in rolesArray){
if(item == "manager"){
return RedirectToAction("index", "Manager");
}
}
return View();
}
You should be able to call .IsInRole()
if (User.IsInRole("manager"))
{
return RedirectToAction("index", "Manager");
}