Cannot login via username - asp.net-mvc

I'm trying to login via UserName using PasswordSignInAsync but the result is even False, this is my code:
public async Task<IActionResult> Login(LoginViewModel vm)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(vm.Email);
var result = await _signInManager.PasswordSignInAsync(user.UserName, vm.Password, vm.RememberMe, false);
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "Wrong Credentials!");
return View(vm);
}
return View(vm);
}
so essentially in my View I fill the email and password fields, and the user object is correctly filled with this information:
UserName = "some_username";
Email = "foo#gmail.com";
I tried to pass both UserName and Email in the first paramter of PasswordSignInAsync but I'm not able to fix this.
I saw also other similar question here, but I don't understand how can I fix this.

Your code is fine. Though, since you are already fetching the user, you can just use that directly:
await _signinManager.PasswordSignInAsync(user, vm.Password, vm.RememberMe, false);
It's important to realize that this method can fail for multiple reasons, not just a bad username/password. 2FA may be required (result.RequiresTwoFactor) or the user could be locked out (result.IsLockedOut). Additionally, if you've required email verification, all login attempts will fail until the email is verified (result.IsNotAllowed). You should be checking for all these conditions and handling each appropriately.

Related

Owin.Providers yahoo FantasySports API Access Token

Very hard to understand how to use Oauth or OWIN for anything besides logging in. I have created an MVC web app. I can have users log into my app using their Yahoo ID (instead of a local ID) just fine. This is done using Owin.Security.Providers.Yahoo
I can also make API calls using DevDefinedOauth, here is a code snippet:
public ActionResult Test2(string oauth_token, string oauth_verifier)
{
if(String.IsNullOrEmpty(oauth_token))
{
oauthButton_Click();
}
else
{
OAuthSession session = (OAuthSession)Session["oAuthSession"];
IToken requestToken = (IToken)Session["oAuthToken"];
if (!String.IsNullOrEmpty(oauth_verifier))
{
IToken accessToken = session.ExchangeRequestTokenForAccessToken(requestToken, oauth_verifier);
Session["oAuthSession"] = session;
}
}
IConsumerRequest playerData = ((OAuthSession)Session["oAuthSession"]).Request().Get().ForUrl("http://fantasysports.yahooapis.com/fantasy/v2/game/371/players");
var xml = playerData.ToDocument();
My problem is I want to eliminate the use of DevDefinedOauth (if possible) and use only Owin.Security.Providers.Yahoo
I cannot find any documentation out there on how to use Owin to do this. Is it possible? I noticed I can try something like this, but it yields no results:
var token = new Owin.Security.Providers.Yahoo.Messages.AccessToken();
string test = token.Token;
I read a post on how to get the access token if you use facebook and Oauth, but the same code doesn't work for Yahoo. I am not sure if there is something that needs to be added to my Startup Class, or my Account controller action (below).
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var user = await UserManager.FindAsync(loginInfo.Login);
if (user != null)
{
await SignInAsync(user, isPersistent: false);
//var client = new Owin.Security.Providers.Yahoo.
return RedirectToLocal(returnUrl);
}
else
{
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { UserName = loginInfo.DefaultUserName });
}
}
As you can see, if you look at my other posts, I am a little lost. I feel like I'm very close to the answer but just can't find it. Woirst case scenario I can use Owin Providers for logging in and then use DevDefinedOauth for API calls but it seems like a lot fo extra code (and usings) for nothing. If anyone has any ideas please let me know....

No user data available after calling SingInManager and returning a success MVC5

I am getting a NullReferenceException when I try to access the User data after a successful login. The inquiry here is about when the user data is actually available to be queried against in the sign in process. Here is the code that I have placed into the Login method of the Account controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
var sh = new SoapHelper();
var user = UserManager.FindById(User.Identity.GetUserId());
Session["UserExists"] = true;
Session["StartDate"] = DateTime.Now.ToString("yyyyMMdd");
Session["EndDate"] = DateTime.Now.ToString("yyyyMMdd");
Session["UserCompany"] = user.CompanyID;
The final line of code is where the error is thrown, am I not able to access the UserManager to get the data I need from the User table at this time in the login process?
Any help will be greatly appreciated!
Do you mean to say that user object is empty ?
var user = UserManager.FindById(User.Identity.GetUserId());
Please can you post the exception here so we can provide more details regarding this.
Also is there CompanyId column in users table in entity framework?
After some more research and testing I found that the reason why you can't use User.Identity.Name in the same call with SignInManager.PasswordSignInAsync is that User.Identity filled out with the claims from authentication cookie (which are not parsed yet).
So I tried a second call with an await to the UserManager
var user = await UserManager.FindAsync(model.Email, model.Password);
and it worked like a charm.

Why Log out after verify phone number in asp.net identity?

I am using asp.net Identity in my project. In VerifyPhoneNumber view, when user confirm his phone number, he is logged out (.AspNetApplicationCookie is removed. I checked this from Resource tab inspect chrome).
Code of VerifyPhoneNumber action in ManageController:
if (!ModelState.IsValid)
{
return View(model);
}
string phoneNumber = UserManager.GetPhoneNumber(User.Identity.GetUserId());
var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), phoneNumber, model.Code);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
ViewBag.Message = "Complete";
return View();
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "something wrong!");
return View(model);
Why this happens?
Update
I have set validateInterval for SecurityStampValidator to 0.
The ChangePhoneNumberAsync has this line:
await UpdateSecurityStampInternal(user).WithCurrentCulture();
Which causes the cookie expiration or re-validation.
If you don't want it, you have to inherit from the UserManager<TUser> class (create your CustomUserManager class) and then override the ChangePhoneNumberAsync method. Just use the same code without the UpdateSecurityStampInternal line.
Changing any security related information on the user (i.e password/phone number/email) automatically causes the cookie to expire by default (via the security stamp for the user getting flipped)

Associate a new Google or Facebook login with an existing account

I have just followed instructions in this article to add Google as a login provider to my MVC 5 app. All seems to work OK, but when I log in via Google, it wants me to register the email/username provided by Google as a new account in my app. If I leave the email as is and click the 'Register' button, it tells me that address is already taken, as I have earlier registered on my app's own login provider.
How can I tweak the default code generated by the MVC project template to allow me to associate the Google login with an existing local account?
P.S. I have exactly the same problem with Facebook.
I totally second the points raised by #Chris Pratt
However i'm not sure the code used is enough to do what the OP asked.
adding this code in the default block inside ExternalLoginCallback should do the job
ApplicationUser user = await UserManager.FindByNameAsync(loginInfo.Email);
if (user != null)
{
var addLoginResult = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);
if (addLoginResult.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}
I think Identity handles it the way it does on purpose, since there's no real way to verify the identity of the user by email alone coming from a third party. While the risk may be relatively low, it is possible that someone could create an account with a third party like Facebook, using an email address that does not belong to them, and then use that third-party login to impersonate an account at another website attached to the same email.
As a result, Identity only lets you create a new account with an external login when signing in, not attach to an existing one. However, once the user is authenticated by other means, methods are provided to associate additional logins.
If you're not concerned by the relatively mild security risk associated with just assuming that if the email matches it's the same person, then you need only modify ExternalLoginCallback in AccountController.cs to attempt to find the user by email:
var user = await UserManager.FindByEmailAsync(loginInfo.Email);
And then sign them in:
await SignInManager.SignInAsync(user);
Here is how I was able to solve this issue that you are having. This solution will allow you to register on the site with your email and if you then try to use Google to log in with the same Email address, you will not be requested to register and you will not generate any errors; you will be allowed to login if the email address that you logged in with locally is the same as your google account email. I edited the Default ExternalLoginCallBack code that VS2015 generated with an if / else statement, which is checking for an existing email that matches the login Email. I hope this helps you with your question, for I had the same issue and could not find an answer anywhere. My multiple post requests were ignored and thankfully, I read one of the answers from this post that led me to my own solution that is working for me on VS2015 Core.
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}");
return View(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
// Sign in the user with this external login provider if the user already has a login.
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
var user = await _userManager.FindByNameAsync(email);
if (user != null)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
else
{
// If user does not already exists, invite User to register.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
if (result.Succeeded)
{
_logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl });
}
if (result.IsLockedOut)
{
return View("Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ViewData["ReturnUrl"] = returnUrl;
ViewData["LoginProvider"] = info.LoginProvider;
email = info.Principal.FindFirstValue(ClaimTypes.Email);
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
}
}
}
TLDR you need to go through all scenarios manually in your ExternalLoginConfirmation function, and have a database table to be able to match membership user id with OAuth user id. This way you can "associate" multiple OAuth accounts with single local account.
Below is a code snippet from one of our projects - hopefully it's clear enough
public ActionResult ExternalLoginCallback()
{
var returnUrl = HttpContext.Request.QueryString["returnUrl"];
var result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
if (result.IsSuccessful == false)
{
return this.View("ExternalLoginFailure", result);
}
// Login user if provider represents a valid already registered user
if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false))
{
return this.RedirectToLocal(returnUrl);
}
// If the current user is logged in already - add new account
if (User.Identity.IsAuthenticated)
{
OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
return this.RedirectToLocal(returnUrl);
}
var membershipUser = Membership.GetUser(result.UserName);
// so user is new - then create new membership account
if (membershipUser == null)
{
MembershipCreateStatus createStatus;
membershipUser = Membership.CreateUser(username: result.UserName, password: this.GetTempPassword(), email: result.UserName, status: out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
this.emailService.SendWelcome(this, (Guid)membershipUser.ProviderUserKey);
// Associate social network account with created membership account
OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, result.UserName);
OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false);
return this.RedirectToLocal(returnUrl);
}
// The problem occured while creating membership account
this.ViewBag.Error = MembershipErrorNameProvider.FromErrorCode(createStatus);
return this.View("CreateMembershipAccountFailure");
}
// If membership account already exists -> Associate Social network account with exists membership account
OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, result.UserName);
OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false);
return this.RedirectToLocal(returnUrl);
}
and OAuthWebSecurity is a helper class which deals with all providers you support:
public static class OAuthWebSecurity
{
....
public static bool Login(string providerName, string providerUserId, bool createPersistentCookie)
{
var context = new HttpContextWrapper(HttpContext.Current);
var provider = GetOAuthClient(providerName);
var securityManager = new OpenAuthSecurityManager(context, provider, OAuthDataProvider);
return securityManager.Login(providerUserId, createPersistentCookie);
}
public static void CreateOrUpdateAccount(string openAuthProvider, string openAuthId, string userName)
{
var user = UserRepository.FindByName(userName);
if (user == null)
{
throw new MembershipUserNotFoundException();
}
var userOAuthAccount = UserOAuthAccountRepository.Find(openAuthProvider, openAuthId);
if (userOAuthAccount == null)
{
UserOAuthAccountRepository.InsertOrUpdate(new UserOAuthAccount
{
OAuthProvider = openAuthProvider,
OAuthId = openAuthId,
UserId = user.Id
});
}
else
{
userOAuthAccount.UserId = user.Id;
}
UserOAuthAccountRepository.Save();
}
}
This is a normal behavior in MVC template, since external logins attempt to create user and if the user's email (or external identity) already exists, attempt to signin. Furthermore, external login provider tries to assign an optional and unique identifier in your application (locally) separate from your external identity. But, the following is strange, as you said:
If I leave the email as is and click the 'Register' button, it tells
me that address is already taken, as I have earlier registered on my
app's own login provider.
Which should work since each user's external id should be unique in other sites (I believe) unless you have registered multiple external accounts with the same identity since the table structures looks like this:
The column UserId will be matched with the bellow table's UserId column:
The message will be showed rarely, when a user tries to assign duplicate Username which will be enumerated with your email or identity name in other sites (e.g. somename#gmail.com will be somename as Username)
This following link shows you how to build an ASP.NET MVC 5 web application that enables users to log in using OAuth 2.0 with credentials from an external authentication provider, such as Facebook, Twitter, LinkedIn, Microsoft, or Google. For simplicity, this tutorial focuses on working with credentials from Facebook and Google existing accounts.
Refer this:
MVC 5 App with Facebook, Twitter, LinkedIn and Google OAuth2 Sign-on (C#)

Redirect user after successful (fake) login in OpenID Offline Provider

Many days ago, I asked this question and I'm set with a working offline OpenID provider (I guess).
What I want is a simple login text box for OpenID identifier which will automatically accept the user and consider the user as logged in, then I want him to be redirected to the main products page (Products => Index).
But, what I didn't know (and didn't find on the internet) is how to go on after the fake authentication process.
I tried to do this:
[HttpPost]
public ActionResult Login(string openid)//openid is the identifier taken from the login textbox
{
var rely = new OpenIdRelyingParty();
var req = rely.CreateRequest(openid);
req.RedirectToProvider();
return RedirectToAction("Index", "Products");
}
First of all, it is not being redirected in the 4th line (instead the login page is refreshed) and second, no authenticated user is really there, I mean when I checked User in Watch window, it is not null but Username is empty and there is no authenticated identity which, I guess, means that there is no cookies set.
P.S:
1. No exceptions are being thrown. But when I try to call FormsAuthentication.RedirectFromLogin() method I get an exception (Server cannot modify cookies after HTTP headers have been set).
2. Index action method is marked with [Authorize] attribute, and so when someone tries to browse Products it is redirected to the login screen, but when he logs in (fake login), shouldn't he be redirected back to Products page?
I tried this also:
[HttpPost]
public ActionResult Login(string openid)
{
var rely = new OpenIdRelyingParty();
return rely.CreateRequest(openid).RedirectingResponse.AsActionResult();
}
But no luck.
What did I miss? and how to make it work as expected? I can depend on myself but I need a decent documentation for OpenID especially for the offline local provider.
Check this example: OpenID and OAuth using DotNetOpenAuth in ASP.NET MVC
public ActionResult OpenId(string openIdUrl)
{
var response = Openid.GetResponse();
if (response == null)
{
// User submitting Identifier
Identifier id;
if (Identifier.TryParse(openIdUrl, out id))
{
try
{
var request = Openid.CreateRequest(openIdUrl);
var fetch = new FetchRequest();
fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
fetch.Attributes.AddRequired(WellKnownAttributes.Name.First);
fetch.Attributes.AddRequired(WellKnownAttributes.Name.Last);
request.AddExtension(fetch);
return request.RedirectingResponse.AsActionResult();
}
catch (ProtocolException ex)
{
_logger.Error("OpenID Exception...", ex);
return RedirectToAction("LoginAction");
}
}
_logger.Info("OpenID Error...invalid url. url='" + openIdUrl + "'");
return RedirectToAction("Login");
}
// OpenID Provider sending assertion response
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
var fetch = response.GetExtension<FetchResponse>();
string firstName = "unknown";
string lastName = "unknown";
string email = "unknown";
if(fetch!=null)
{
firstName = fetch.GetAttributeValue(WellKnownAttributes.Name.First);
lastName = fetch.GetAttributeValue(WellKnownAttributes.Name.Last);
email = fetch.GetAttributeValue(WellKnownAttributes.Contact.Email);
}
// Authentication
FormsAuthentication.SetAuthCookie(userName: email, createPersistentCookie: false);
// Redirection
return RedirectToAction("Index", "Products");
case AuthenticationStatus.Canceled:
_logger.Info("OpenID: Cancelled at provider.");
return RedirectToAction("Login");
case AuthenticationStatus.Failed:
_logger.Error("OpenID Exception...", response.Exception);
return RedirectToAction("Login");
}
return RedirectToAction("Login");
}
Basically, your action method is called twice:
The first time by your form being submitted by the user.
The second time is a call back (redirect) from the OpenID provider.
This time, there will be a value for the response. If response.Status is valid, you can log your user in using the FormsAuthentication class and finally you can redirect him to your main products page.

Resources