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

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)

Related

Cannot login via username

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.

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.

AddToRole not updating User role

Okay first I just want to say what I'm trying to do has turned out to be a real PITA. The issue I'm having is similar to the following posts:
ASP.NET Identity check user roles is not working
Updating user role using asp.net identity
public async Task<ActionResult> MyAccount()
{
var userId = User.Identity.GetUserId();
var user = await UserManager.FindByIdAsync(userId);
if (!User.IsInRole(RoleConst.EXPIRED))
{
await UserManager.AddToRoleAsync(userId, RoleConst.EXPIRED);
await SignInAsync(user, false);
}
var isExpired = User.IsInRole(RoleConst.EXPIRED); // FALSE!!
return View(model);
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
var authenticationManager = HttpContext.GetOwinContext().Authentication;
authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.TwoFactorCookie);
authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, await user.GenerateUserIdentityAsync(UserManager));
}
The role does not update even after using the sign in method to refresh the cookie as some other users have suggested. Of course the role is updated in the db.
This code works when checking the role after updating:
var isExpired = UserManager.IsInRole(userId, RoleConst.EXPIRED);
I would be fine with that I guess, however, I need to do this check immediately after in my razor Views. I haven't found how I can use the UserManger outside of the controller. Any suggestions to this seemingly simple task would be appreciated!
EDIT
I've also tried the following which yields the same result:
await UserManager.AddToRoleAsync(user.Id, RoleConst.EXPIRED);
await UserManager.UpdateSecurityStampAsync(user.Id);
var isExpired = User.IsInRole(RoleConst.EXPIRED); // FALSE
It will work once you Sign out and signing in again (which is not a user-friendly option)
So try this (reordered these lines),
await this.UserManager.AddToRoleAsync(user.Id, model.UserRole);
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
So that the cookies get stored only after the user role gets added. :)
One person in the linked questions suggested signing out and signing in again, which you aren't even doing: just signing in. However, in one of the comments you can find a link to the answer you need. Long and short you need to call:
UserManager.UpdateSecurityStampAsync(userId);
After changing the user's roles.

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