How to get user information after successful authentication? - c#

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.

Related

Should I check authentication first or ModelState.IsValid

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

Retrieving T from Task<T>

I'm working on ASP.NET MVC5 app based around Parse.com framework.
Since i can't use Parse login method i had to use method posted here to work around its limitations: Parse.com multiple users issue
Here is my login method(just minor changes):
public async Task<ActionResult> Login(AccountModel model) //no returnUrl string
{
ParseUser user;
try
{
user = await ParseUser.LogInAsync(model.UserName, model.Password);//login parse user
}
catch (Exception e)
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
//making setAuthCookie get parse object id instead of username
FormsAuthentication.SetAuthCookie(user.ObjectId, model.RememberMe);
ParseUser.LogOut(); //log out parse user
return RedirectToAction("index", "home"); //Redirect to Action
}
So basically i (parse)login user, set AuthCookie to it's object id and then (parse)logoff user. That way i can have multiple users logged in.Out of SetAuthCookie i can get users id now.
However i'd like to display some extra user data(like user adress, Type, Name, LastName) that is on parse.com cloud. So i figured i will just write a method that will get this data by using currently authenticated userID, fill my AccountModel class object with data and then pass it to views. This is a loose idea of how it'd look like(i know syntax is probably wrong, i don't have access to my Visual studio right now):
UserData model:
public async Task<AccountModel> GetUserData()
{
AccountModel userData = new AccountModel();
ParseObject user;
ParseQuery<ParseObject> query = ParseObject.GetQuery("_User");
try
{
//i can't remember how to get authenticated user identity
user = await query.GetAsync(AuthenticatedUser.Identity());
}
catch(Exception e)
{
//code to handle exception
}
userData.Name = user.Get<string>("Name");
userData.Lastname = user.Get<string>("Lastname");
userData.Adress = user.Get<string>("Adress");
return userData; //it will probably throw an error
}
Controller:
public ActionResult Index()
{
UserData model = new UserData();
return View(model.GetUserData());
}
So now it will probably throw an error(can't return T from Task< T >) and i have no idea how to fix this, so i can get currently logged in user data.
I have nav bar on my site where user name and last name is displayed, so i have to somehow get currently logged in user data every time page is displayed. Is there any work around/easier way to achieve this?
You fix this by making your Action asynchronous as well:
public async Task<ActionResult> Index()
{
UserData model = new UserData();
return View(await model.GetUserData());
}
Async goes "all the way". This means that once you have an asynchronous method that needs to be awaited, it will usually cause most (if not all) of your stack-trace to be asynchronous as well.
Side note:
Once should stick to .NET conventions and mark async methods with the XXXAsync postfix, so your method should actually be named GetUserDataAsync.

ASP.NET MVC 4 Facebook FacebookContext - How to get reference

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

SimpleMembership changing username

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.

MVC Redirection in OnAuthorization causes authorization to fail

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.

Categories