I have a .NET MVC5 website, where the user is logged in using Microsoft Identity. I have multiple form posts for adding and editing items across the site. I'm looking to know which order I should perform validation in:-
ModelState.IsValid and then User.Identity.IsAuthenticated
User.Identity.IsAuthenticated then ModelState.IsValid
I currently have the following code which works, but it seem to be a case of 'the chicken and egg':-
var user = UserAccountFunctions.GetUser(User);
if (user != null)
{
ClientProfile profile = ClientProfile.GetUser(user.Id, db);
if (profile != null)
{
if (ModelState.IsValid)
{
// Do logic here
}
}
}
Should I swap this code round to check the model first, before checking authentication so that I have:-
if (ModelState.IsValid)
{
var user = UserAccountFunctions.GetUser(User);
if (user != null)
{
ClientProfile profile = ClientProfile.GetUser(user.Id, db);
if (profile != null)
{
// Do logic here...
}
}
}
Or is there simply no difference here? I repeat this code a lot throughout the site, so looking for which is the better option? I currently use the top one, because I feel that you shouldn't even attempt to check the model unless they are authenticated?
Any advice here?
Thanks!
Here is example of updating users's email:
[AcceptVerbs(HttpVerbs.Post)]
[Authorize]
[ValidateAntiForgeryToken()]
public ActionResult emailupdate(UserEmailEditModel editmodel_post)
{
if (!ModelState.IsValid)
{
// redirect to email view and show errors
}
// check if posted id is the same as stored in session
if (User.Identity.GetUserId() != editmodel_post.user_id.ToString())
{
// redirect to email view and show errors
}
}
So
Use Authorize attribute
Use ValidateAntiForgeryToken attribute
Check ModelState
Check against session or database
Related
I have started learning C# MVC 5 for a Room Inventory project i am working on where i work.
I am using active directory to authenticate users and then store these details in a database so that i can then assign users to rooms and tenancies that have items assigned to them.
I used the stock MVC 5 template that comes with Visual Studio 2013 as i only have a month to do the project coming in to it with no previous knowledge and dont have the time to code a fancy looking html / css front end and am relying on the look that comes out of the box.
I have written some code that once a user successfully logged on using form authentication linked with AD it runs a check to see if a user exists in the database, if not it creates one, if it does, it checks to see if it has been set to inactive and reactivates it if needed.
I have placed this code in the AccountController under the login httppost action. As i am new to MVC i wanted to check to see if this is the correct position, or if i should create a new class for this functionality, or if it should be in the model area where i create the database using entity framework code first. What is the best practice?
Here is the code and thanks in advance for any help. I apologize for the long winded post and less than stellar code. Dont hold back with any criticism as i would rather get it right now than make mistakes again and again.
[HttpPost]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Put this method in place to allow multiple domains for login. References Web.config for providers
MembershipProvider domainProvider;
switch (model.Domain)
{
case "Student":
domainProvider = Membership.Providers["studentADMembershipProvider"];
break;
case "Staff":
domainProvider = Membership.Providers["staffADMembershipProvider"];
break;
default:
throw (new Exception("This domain is not supported"));
}
// Method for authenticating users on AD to allow system integration and also add or update user in Users Database
if (domainProvider.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
//Code for creating new user based on successfull logged in user.
//define which domain to link too. otherwise will always default to staff
PrincipalContext myContext;
if (model.Domain == "Staff")
{
myContext = new PrincipalContext(ContextType.Domain, "staff.domain.com");
}
else
{
myContext = new PrincipalContext(ContextType.Domain, "student.domain.com");
}
UserPrincipal aduser = UserPrincipal.FindByIdentity(myContext, IdentityType.SamAccountName, model.UserName);
//Check for existence of user with username that matches loged in user
var userSearch = db.Users.Count(b => b.Username == aduser.SamAccountName);
if (userSearch == 0)
{
// User does not exist, therefore create user
User user = new User()
{
IsActive = 1,
Username = aduser.SamAccountName,
FirstName = aduser.GivenName,
LastName = aduser.Surname,
Extension = aduser.VoiceTelephoneNumber,
Email = aduser.EmailAddress,
UserTypeId = 3
};
db.Users.Add(user);
db.SaveChanges();
}
else
{
// User does exist, but has been deactivated, therefore reactivate
var activateUser = db.Users.FirstOrDefault(d => d.Username == aduser.SamAccountName);
if (activateUser.IsActive == 0)
{
activateUser.IsActive = 1;
db.SaveChanges();
//db.Users(activateUser.UserId).State = EntityState.Modified;
}
}
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError(string.Empty, "The user name or password provided is incorrect.");
return View(model);
}
Today I've been trying to program a little bit in the MVC 4 Facebook API developed by Microsoft (based on the example of: http://www.asp.net/mvc/tutorials/mvc-4/aspnet-mvc-facebook-birthday-app)
So far I managed to manipulate the MyAppUser model, etc. Everything working fine and as intended. I only have a slight problem when I'm switching through controllers.
Is there any way to retain the FacebookContext object through controllers?
Unfortunately the above example (from Microsoft) only loads MyAppUser in the Home controller as follows:
[FacebookAuthorize("email", "user_photos")]
public async Task<ActionResult> Index(FacebookContext context) {
if (ModelState.IsValid) {
var user = await context.Client.GetCurrentUserAsync<MyAppUser>();
return View(user);
}
return View("Error");
}
What should I do if I use another controller in the application? How can I obtain a FacebookContext reference to get the user?
Things I tried:
Putting FacebookContext context into the other Controller (is always null)
Putting the FacebookContext object into Session or ViewBag - no avail, and sounds way too dirty anyway.
Am I missing something crucial here?
I just wanted to have a different Controller with a couple of actions to manage a User's profile, which would be done completely separately from Facebook's data (via a database hosted locally.) The only reason I need to load the Context is to get the current user's e-mail address to create their account on that basis.
Any help would be greatly appreciated as I've spent quite a considerable amount of time trying to fix it.
My example controller could be:
public ActionResult Manage()
{
var user = await context.Client.GetCurrentUserAsync<Models.MyAppUser>();
if (MyDALFunction.GetUserByMail(user.Email) == null) {
// Create user functions, create a ViewModel, pass it on and do some editing.
}
return View(user);
}
This is how I solved this:
First, in Home Controller I save access token to TempData
public async Task<ActionResult> Index(FacebookContext context)
{
if (ModelState.IsValid)
{
this.TempData["accessToken"] = context.AccessToken;
Then I read it in another action in different controller. If access token is empty, it means that user is not logged in, so I redirect him to Home controller.
var accessToken = TempData["accessToken"] as string;
if (string.IsNullOrEmpty(accessToken))
{
//if access token is null or user is not logged in, redirect to home controller
return RedirectToAction("Index", "Home");
}
else
{
var fb = new Facebook.FacebookClient(accessToken);
var me = fb.Get("me") as Facebook.JsonObject; //current logged user
var userFacebookId = me["id"].ToString();
Instead of "id", you can read email.
EDIT:
Retrieving accessToken from TempData returned null, when i tried to do that in another controller. It would be better to store it in Session instead.
Where to store Facebook access token in ASP.NET MVC?
Sorry, for answering too late.. but if i understood your question correctly then i think you are trying to get the FacebookContext object in you Action Method when post-back occurs. If so, then.. In your .cshtml try to put
<a target="_top" href="#GlobalFacebookConfiguration.Configuration.AppUrl#Url.Action("Manage", new { friendId = friend.Id })" role="button" class="btn btn-success">
and then make you action method like...
public ActionResult Manage(string friendId, FacebookContext context)
{
var friend = await context.Client.GetFacebookObjectAsync<MyAppUserFriend>(friendId);
// var user = await context.Client.GetCurrentUserAsync<Models.MyAppUser>();
if (MyDALFunction.GetUserByMail(friend.Email) == null) {
// Create user functions, create a ViewModel, pass it on and do some editing.
}
return View(user);
}
But Make sure that your MyAppUserFriend model have the Email attribute..
If you wanted any thing else then please provide some detail of you Model and your View
The goal
Get the user information after successful authentication.
The problem
Take a look in the following fragment of code:
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public ActionResult Authenticate(User userModel)
{
if (ModelState.IsValid)
{
if (userModel.IsValid(userModel.Email, userModel.Password))
{
FormsAuthentication
.SetAuthCookie(userModel.Email, userModel.Remember);
return RedirectToAction("Index", "Manager");
}
else
{
ModelState.AddModelError("", "Login data is incorrect!");
}
}
return RedirectToAction("Index");
}
As you can see, it is a normal authentication's controller. What I need is simple: if the statement userModel.IsValid is true, how can I get the user information based on the email that he sent to the server by userModel.Email?
Maybe store the email on the session and in the Index method call some method to get the (user) information passing through parameter the email that inhabiting the session? (I think this isn't the best way because if the cookie exist and the session not, there will be a problem here.)
Code spotlight
To get information of some user, I'm using a simple method: User.Invoke((string) userEmail).
Knowledge improvement
I'm logging in on my website with email and password as various applications of the world do. With the email that the user enters, I'm attempting to get his information from database. So I ask: is this the best way to do this? Maybe isn't better firstly get the ID of the user by his email and then select his information?
What I already tried
In the Authenticate method (the same that I passed before), I implemented the following code:
[...]
public ActionResult Authenticate(User userModel)
[...]
if (userModel.IsValid(userModel, userModel.Password))
{
FormsAuthentication
.SetAuthCookie(userModel.Email, userModel.Remember);
Session["UserEmail"] = userModel.Email; // <-- Pay attention to this
return RedirectToAction("Index", "Manager");
}
[...]
}
And then, in the Index method:
public ActionResult Index()
{
if(Request.IsAuthenticated())
{
UserProfile user = User.Invoke(Session["UserEmail"]));
return View(user);
}
else
{
return View();
}
}
But as I said, if the cookie that flags that the user is logged in is alive and the session not, there will be a problem right here — a kind of concept conflict (cookie vs. session).
What can I do?
The accepted answer (from the discussion with the OP) is : the most straightforward way of retrieving the user name set by the forms authentication module is to use
HttpContext.Current.User.Identity.Name
or just
this.User.Identity.Name
in a controller.
I'm using SimpleMembership in an MVC4 app and need the ability for a user to change their own username.
I have the functionality working, so when a user changes their username it works. However when invoking things like Roles.IsUserInrole() then it fails as User.Identity.Name is set to what they logged in as, not the new value. That value no longer exists in the database, as they have changed their name.
I can't see a method to update the logged in user context with a username. For the most part I can store the users ID in session and retrieve it when doing queries, but I'm using the Roles method to display data in a view which fails.
Any ideas?
Thanks!
This is my current (working) solution:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(AccountEditModel model)
{
if (ModelState.IsValid)
{
if (HasSensitiveInformationChanged(model)) // model.EmailAddress.ToLower() != User.Identity.Name.ToLower()
{
if (Membership.ValidateUser(User.Identity.Name, model.Password)) // && WebSecurity.IsCurrentUser(User.Identity.Name)) //redundant?
{
using (UsersContext db = new UsersContext())
{
UserProfile user = db.UserProfiles.FirstOrDefault(u => u.EmailAddress.ToLower() == User.Identity.Name.ToLower());
if (user != null)
{
user.EmailAddress = model.EmailAddress;
db.SaveChanges();
WebSecurity.Logout();
WebSecurity.Login(model.EmailAddress, model.Password);
return RedirectToAction("Index", "Search");
}
else
{
ModelState.AddModelError("", "Could not find user. Please try logging in again.");
}
}
}
else
{
ModelState.AddModelError("","Could not change email address. Please verify your password and try again.");
}
}
else
{
//no change
return RedirectToAction("Index", "Search");
}
}
return View("Index", model);
}
I didn't think you were able to change the username field (would be nice if you could show me how you achieved that).
The only solution I see is that you force the user to log off after changing the username, so when they log back in they will have the correct User.Identity.Name.
I'm trying to redirect the user to a different action if their email address has not been validated. The thing is, I don't want them to be logged out, I just want to redirect them. When I do this in OnAuthorization for the controller, it redirects as expected, but the user is not authenticated. I'm not sure why this is. My code looks like this:
protected override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
//_applicationService.CurrentUser is populated correctly at this point
// from Controller.User
if (_applicationService.CurrentUser != null)
{
if (_applicationService.CurrentUser.EmailVerified != true)
{
var url = new UrlHelper(filterContext.RequestContext);
var verifyEmailUrl = url.Action("EmailVerificationRequired", "Account", null);
filterContext.Result = new RedirectResult(verifyEmailUrl);
}
}
}
Note: I've removed unnecessary code to make it clearer. _applicationService.CurrentUser is populated with the current user - and the user has been authenticated correctly when it gets to that point. But after the redirect the user is no longer authenticated.
How can I achieve this redirect without affecting the built in user authorization?
I've tried putting my code into OnActionExecuting, and I've also tried implementing it in a custom ActionFilterAttribute as well, but wherever I put this redirect in it prevents the 'User' (ie: System.Security.Principal.IPrincipal Controller.User) from getting authenticated.
What am I missing here? Hope this makes sense. Any help much appreciated.
In response to Darin's request for my login action:
[HttpPost]
[AllowAnonymous]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
string errorMessage = "The username or password is incorrect";
if (ModelState.IsValid)
{
if (_contextExecutor.ExecuteContextForModel<LoginContextModel, bool>(new LoginContextModel(){
LoginViewModel = model
}))
{
ViewBag.CurrentUser = _applicationService.CurrentUser;
_formsAuthenticationService.SetAuthCookie(model.LoginEmailAddress, model.RememberMe);
if (_applicationService.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home").Success("Thank you for logging in.");
}
else
{
errorMessage = "Email address not found or invalid password.";
}
}
return View(model).Error(errorMessage);
}
Okay, I've now found where I was going wrong. The problem was that I was being a bit of a goon and I didn't fully understand what was happening when I was doing:
filterContext.Result = new RedirectResult(verifyEmailUrl);
I didn't realise that I am actually starting a new request with this, I mistakenly thought that I was just redirecting to another action. It seems obvious now that this will be a new request.
So, the problem was that my EmailVerificationRequired action was not authorizing the user, and thus when it got to this action the current user was null. So the fix was add the authorization to that action and now it's all fine.
Thanks for your help guys.
You can handle this in your login actionresult. Try placing the
if (_applicationService.CurrentUser.EmailVerified != true)
{
FormsAuthentication.SignOut();
return RedirectToAction("EmailVerificationRequired", "Account");
}
code immediately after this line:
_formsAuthenticationService.SetAuthCookie(model.LoginEmailAddress, model.RememberMe);
Next, set breakpoints and step through the Login action. If you do not reach the if (_applicationService.CurrentUser.EmailVerified != true) line then it indicates that your user is not authenticated, and you have a different problem to address.