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.
Related
I have the following register method, and I swear I (manually) tested this a while back and noted that if a username already existed, the result simply had a false value for result.Succeeded and would append the error message to the ModelState (using the build in AddErrors(result) helper method). I'm pretty sure this method (Register(...)) comes out of the box with ASP.NET mvc 5, but I think I changed the user to include a username (whereas out of the box, the email is simply used as the username).
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Username, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Instead, I am currently getting the error as an EntityValidationError being thrown and uncaught.
I know I could simply catch this error and move on with my day, but I want to make sure that something else is causing this issue if it is not the correct behavior.
Update:
After creating a new MVC project, I can confirm that the typical behavior (when a duplicate username is registered) is that CreateAsync should return a result with a false value for result.Succeeded and an error message of "Username is taken" should be appended to the ModelState. Clearly something is amiss in my code or config, but I haven't the foggiest idea of where to start exploring. If it helps, I have been seeing EntityValidationErrors in other places of my code lately in situations that shouldn't warrant it either. See: Unable to SaveChanges on a db update. Weird lazy loading behavior possibly?
I found my own solution. As I mentioned, I had altered the user to include a username (as well as make email optional). A part of this task involved creating a custom user validator class. In the custom user validator's ValidateAsync method, I had forgotten to check if a username existed already (and did not belong to the user). Like so:
async Task<IdentityResult> IIdentityValidator<TUser>.ValidateAsync(TUser item)
{
var errors = new List<string>();
// ...
// Piece of code I have now added
var owner = await _manager.FindByNameAsync(item.UserName);
if (owner != null && !EqualityComparer<string>.Default.Equals(owner.Id, item.Id))
{
errors.Add($"Username {item.UserName} is already taken");
}
// End of code I added
// ...
return errors.Any()
? IdentityResult.Failed(errors.ToArray())
: IdentityResult.Success;
}
I believe the lesson learned for me is the difference between the App layer validation, where validation occurs in the CreateAsync method by the UserManager. In the case of the App layer validation, the error will present itself exactly as prescribed. If that layer of validation is omitted, and the DB faces the same constraint, then when the context is saved, it will throw its own error. In this case, a slightly more cryptic EntityValidationError.
I'm having an issue with the ForgotPassword method for the base asp.net identity. When stepping through the code, the line var user = await UserManager.FindByNameAsync(model.Email); returns null, even though I have confirmed that the email address for the user exists in the aspnetusers table. I'm not sure why Visual Studio will not allow me to step into the FindByNameAsync method? Not sure what's going on here?
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
{
// Don't reveal that the user does not exist or is not confirmed
return View("ForgotPasswordConfirmation");
}
var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = Url.Action("ResetPassword", "Account",
new { UserId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Reset Password",
"Please reset your password by clicking here: link");
return View("ForgotPasswordConfirmation");
}
// If we got this far, something failed, redisplay form
return View(model);
}
You are trying to find an user by an email address.
You should use UserManager.FindByEmailAsync
This usually happens when you create the user using some other method than CreateAsync in Microsoft.AspNetCore.Identity.UserManager. I had the same issue because I was creating the users directly through EF, not the referred method.
All FindBy methods should work properly using this approach.
I had a similar issue for the project based on ASP.NET Core 2.2. Maybe my solution will be useful for someone.
The user can change their UserName in the UserProfile component (by default, the UserName was the same as Email, i.e., user1#mail.com). If the user changed their Username in the profile from the default user1#mail.com to user1, then they could not log in using this new UserName, only Email.
The line below always returned NULL.
var user = await _userManager.FindByNameAsync(request.UserName);
After investigating the AspCore repository, I found FindByNameAsync method. I become suspicious about NormalizeName line. And my current model for the UserProfile model had only UserName property, which was mapped later using Automapper and saved to the database. So I added computed NormalizedUserName property and also mapped it with Automapper (_mapper.Map(UserProfileModel, dbUser);) and saved it to the database.
public string NormalizedUserName
{
get
{
return UserName.ToUpper().Normalize(); // `UserManager` UserFindByNameAsync method is using `normalizedName` = `NormalizedUserName` from Users table (for some reason UPPERCASE, maybe SQL performance), otherwise we will always get NULL
}
}
Changes mentioned above solved my issue for NULL when using the FindByNameAsync method.
This can happen when the User table has a Query Filter applied to it and the filter criteria is not met.
I am using the following code to generate a reset Token
var token = WebSecurity.GeneratePasswordResetToken(user);
it worked the first time, in that it updated the webpages_Membership table, and updated the following fields,
PasswordVerificationToken
PasswordVerificationTokenExpirationDate
but when I run the above code again, the token returned is the same, and nothing is updated.
how does it work?
The token will only refresh once it has been used with WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword);. If you do not complete the full password reset process, the same token will always be generated for that specific user.
If the password is not reset within 24 hours (default), then the above method will return false. If you want to override the default password expiration, you can add the optional parameter tokenExpirationInMinutesFromNow when calling WebSecurity.GeneratePasswordResetToken :
public static string GeneratePasswordResetToken(
string userName,
int tokenExpirationInMinutesFromNow
)
If you want to catch a bad password change attempt, just wrap WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword); in a try catch block, and you'll be able to display an error to the user :
[HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public ActionResult ForgotPassword(ForgotPasswordModel model)
{
if (ModelState.IsValid)
{
try
{
//Reset password using the reset token and the new password
WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword);
//Redirect to the home account page.
return RedirectToAction("Index", "Home");
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, LocalizedText.Account_Reset_Password_Error);
}
}
//Something bad happen, notify the user
return View(model);
}
I won't worry to much about catching a specific exception here, because the fix will be the same either way - they'll need to generate another password reset token.
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.