I have implemented a custom UserStore, it implements IUserStore<DatabaseLogin, int> and IUserPasswordStore<DatabaseLogin, int>.
My Login action method is as below:
if (ModelState.IsValid)
{
if (Authentication.Login(user.Username, user.Password))
{
DatabaseLogin x = await UserManager.FindAsync(user.Username, user.Password);
DatabaseLogin Login = Authentication.FindByName(user.Username);
if (Login != null)
{
ClaimsIdentity ident = await UserManager.CreateIdentityAsync(Login,
DefaultAuthenticationTypes.ApplicationCookie);
AuthManager.SignOut();
AuthManager.SignIn(new AuthenticationProperties
{
IsPersistent = false
}, ident);
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "Invalid Login");
}
}
return View();
In the custom authentication class that I wrote, Authentication, I have a Login method that works fine, also FindByName method returns an app user. But if I try to SignIn with that login, the user isn't recognized as authenticated and HttpContext.User.Identity is always null, so I imagine that I have to try UserManager.FindAsync.
This method calls FindByNameAsync and GetPasswordHashAsync, and it always return null.
public Task<DatabaseLogin> FindByNameAsync(string userName)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName");
return Task.FromResult<DatabaseLogin>(Authentication.FindByName(userName));
}
public Task<string> GetPasswordHashAsync(DatabaseLogin user)
{
if (user == null)
throw new ArgumentNullException("user");
return Task.FromResult<string>(user.Password);
}
And the Authentication.FindByName
public static DatabaseLogin FindByName(string name)
{
string GetUserQuery = string.Format(
"USE db;SELECT principal_id AS id, name as userName, create_date AS CreateDate, modify_date AS modifyDate FROM sys.database_principals WHERE type='S' AND authentication_type = 1 AND name = '{0}'"
, name);
DatabaseLogin user;
using (var db = new EFDbContext())
{
user = db.Database.SqlQuery<DatabaseLogin>(GetUserQuery).FirstOrDefault();
}
user.Password = Convert.ToBase64String(Encoding.ASCII.GetBytes("pass"));
return user;
}
As you can see I'm using database users, I'm not sure how I can retrieve a hashed password for them. For now, I'm just storing the Base65 of the correct password!
I have no idea where I'm going wrong, any guidance is welcome.
Short answer: nothing's wrong. User is authenticated in other action methods, but apparently not in the current action method.
This is the process that I followed, maybe it will help you debug your app.
After reading the source code, FindAsync first calls FindByNameAsync, followed by CheckPasswordAsync which references VerifyPasswordAsync. So it should be fine If I could override VerifyPasswordAsync.
I created a custom password hasher that implements IPasswordHasher, and registered it in the create method of my UserManager like this:
manager.PasswordHasher = new DbPasswordHasher();
So by now, I can get my user from UserManager.FindAsync, but it turned out that it doesn't matter where you get the user since HttpContext.User.Identity is still null! My mistake was that I didn't notice the user isn't authenticated in the current action, in other action methods it works as expected!
Related
I have a fairly standard Login action, but I want to change the redirect depending on the user role.
However, there's some sort of race condition going on: HttpContext.User says it yielded no results, causing the admin user to be redirected to the wrong homepage.
How do I 'wait' correctly until the HttpContext.User is available after signing in?
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginDto loginDto)
{
if (!ModelState.IsValid)
{
return View(loginDto);
}
var result = await _signInManager.PasswordSignInAsync(loginDto.Username, loginDto.Password, true, false);
if (!result.Succeeded)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(loginDto);
}
// This check doesn't always work because User = null
if(HttpContext.User.IsInRole(RoleEnum.Administrator.ToString())){
return LocalRedirect(Url.Action("Index", "Home", new { area = "Admin" }));
}
return LocalRedirect(loginDto.ReturnUrl ?? Url.Action("Index", "Home"));
}
User UserManager instead of HttpContext in this scope like below
var userInRole = await _userManager.IsInRoleAsync(user, role);
I'm able to authenticate using anything for a password. The email has to be a valid registered email, but the pwd doesn't matter. Everything else is working normally.
Any suggestions on where to start trouble shooting this? I haven't found any similar issues in web searches.
My view...
My action in the account controller...
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(AccountLoginModel viewModel)
{
if (!ModelState.IsValid)
return View(viewModel);
var user = _manager.FindByEmail(viewModel.Email);
if (user != null)
{
await SignInAsync(user, viewModel.RememberMe);
string uid = user.Id;
return RedirectToLocal(viewModel.ReturnUrl);
}
ModelState.AddModelError("", "Invalid username or password.");
return View(viewModel);
}
and the signinasync method...
private async Task SignInAsync(IdentityUser user, bool isPersistent)
{
// Clear any lingering authencation data
FormsAuthentication.SignOut();
// Create a claims based identity for the current user
var identity = await _manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
// Write the authentication cookie
FormsAuthentication.SetAuthCookie(identity.Name, isPersistent);
}
I did create a seperate MVC web project to see the scaffolded login action, which is quite a bit different. The SmartAdmin template is customized enough that its difficult to start changing things without knowing what I'm effecting. Any direction is appreciated.
If username in your system is email, you should use
var user = _manager.FindAsync(viewModel.Email, viewModel.Password);
and then signin the user if it's not null.
If username is not email, you should first get the user and then check for password
var user = _manager.FindByEmail(viewModel.Email);
bool isPasswordCorrect = await _manager.CheckPasswordAsync(user, viewModel.Password);
I have a piece of code that lets an admin add a new admin via email. I check if the user is in our system. If it is, i check if it's already an admin. The odd part is that users that already have the claim 'admin' will still be added as an admin, effectively giving them another same claim in the database. After some debugging I found that IsInRoleAsync will always return false. What could the cause of this be?
public async Task<IActionResult> VoegAdminToe(VoegAdminToeViewModel vam)
{
if (ModelState.IsValid)
{
Gebruiker g = _gebruikerRepository.GetByEmail(vam.Email);
if (g != null)
{
bool isAdmin = await _userManager.IsInRoleAsync(g, "admin");
if (!isAdmin)
{
await _userManager.AddClaimAsync(g, new Claim(ClaimTypes.Role, "admin"));
return RedirectToAction(nameof(Index));
}
ModelState.AddModelError("GebruikerAlAdmin", "Deze gebruiker is al Admin");
return View(vam);
}
ModelState.AddModelError("GebruikerNull","Deze gebruiker zit niet in het systeem");
return View(vam);
}
else
{
return View(vam);
}
}
My guess would be that the function IsInRoleAsync will not go looking in the table AspNetUserClaims, but I'm not quite sure if there's another method to check for this.
IsInRoleAsync is failing in your case because you're passing an entire object, rather than the UserId.
bool isAdmin = await _userManager.IsInRoleAsync(g, "admin");
So, you should actually be passing the UserId field of the Gebruiker object, rather than the object itself.
MSDN
I've resolved the issue by using GetClaimsAsync:
IList<Claim> claimsUser = await _userManager.GetClaimsAsync(g);
bool isAdmin = claimsUser.FirstOrDefault(c => c.Value == "admin") != null;
I am using Asp.net identity for Login,Register,Forgot Password etc and source code is taken from this below link:
http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-web-app-with-email-confirmation-and-password-reset
http://www.asp.net/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity.
Now i have 1 table that is UserMaster and during registration i am asking for this following fields:
FullName,EmailId,Password,ContactNumber,Gender.
My UserMaster Contains this following fields:Id,FullName,EmailId,ContactNumber,Gender
Now when user will submit registration form this FullName,EmailId,ContactNumber,Gender will be saved in UserMaster along with the Email,Password will be saved in AspnetUser.
My Register Method is same as provided in above 2 links.
Here you might notice that there is no relationship between my UserMaster and AspnetUser so during login when user will enter his email id to login i will use this method await SignInManager.PasswordSignInAsync to verify user and if this method returns success then what i will do is use this email id and check this email in my UserMaster and where match will be found i will fetch that UserId from UserMaster and store it in session and use thorugh out my application in my login method like below:
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:
using (var context = new MyEntities())
{
var fetchUSerId = context.UserMaster.Where(t => t.Email == model.Email).Select(t=>t.UserId).SingleOrDefault();
Session["UserId"] = fetchUSerId;
}
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
I am talking about this in my login method:
case SignInStatus.Success:
using (var context = new MyEntities())
{
var fetchUSerId = context.UserMaster.Where(t => t.Email == model.Email).Select(t=>t.UserId).SingleOrDefault();
Session["UserId"] = fetchUSerId;
}
Is this an appropriate way or still a better way and i want to store entire user object instead of just storing User Id.
So can anybody tell me how to do this with aspnet identity??
Since you are using Asp.Net Identity, you want to store session related stuff as claims. This is very easy to extend with customised claims.
As an aside, I think you'd be better off simple extending ApplicationUser to hold the additional data, as detailed here.
That said, here is a complete example of how to add custom claim types to your application.
Step 1 - Define one or more custom claim types to hold your additional information
public static class CustomClaimTypes
{
public const string MasterFullName = "http://schemas.xmlsoap.org/ws/2014/03/mystuff/claims/masterfullname";
public const string MasterUserId = "http://schemas.xmlsoap.org/ws/2014/03/mystuff/claims/masteruserid";
}
A claim type is just a unique string that identifies the specific claim. Here we are just using a similar format as the built in claim types.
Step 2 - During the sign in process, set values for the custom claim types
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
//Fetch data from the UserMaster table
var userdata = GetdatafromUserMaster();
//Using the UserMaster data, set our custom claim types
identity.AddClaim(new Claim(CustomClaimTypes.MasterUserId, userdata.UserId));
identity.AddClaim(new Claim(CustomClaimTypes.MasterFullName, userdata.FullName));
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
Note: we are using custom claim types so that we preserve the existing NameIdentifier and Name claims, and can therefore easily access identity information from both Asp.Net Identity and our custom UserMaster table.
Step 3 - Add extension method(s) to IIdentity so we can easily access our custom claim data
public static class IdentityExtensions
{
public static string GetMasterUserId(this IIdentity identity)
{
if (identity == null)
return null;
return (identity as ClaimsIdentity).FirstOrNull(CustomClaimTypes.MasterUserId);
}
public static string GetMasterFullName(this IIdentity identity)
{
if (identity == null)
return null;
return (identity as ClaimsIdentity).FirstOrNull(CustomClaimTypes.MasterFullName);
}
internal static string FirstOrNull(this ClaimsIdentity identity, string claimType)
{
var val = identity.FindFirst(claimType);
return val == null ? null : val.Value;
}
}
Nothing fancy here. We just cast the IIdentity as a ClaimsIdentity and then return the value of either the first claim of the given CustomClaimType that we find, or we return null if a claim doesn't exist.
Step 4 - Now we can access our custom claim data in views and/or controllers really easily. Say you wanted to use the full name from your UserMaster table instead of the ApplicationUser? You can now do this:
<ul class="nav navbar-nav navbar-right">
<li>
#Html.ActionLink("Hello " + User.Identity.GetMasterFullName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
</li>
<li>Log off</li>
</ul>
You can also do the same thing from within a Controller.
You can add as:
var listClaims=new[] { new Claims(ClaimsType.SerialNumber,Id), new Claims(ClaimsType.Name,FullName), new Claims(ClaimsType.HomePhone,ContactNumber), new Claims(ClaimsType.Gender,Gender)};
var oAuthIdentity=new ClaimsIdentity(listClaims, otherparameter ...);
For more details you can check System.Secutity.Claims.ClaimTypes
you may do this:
var fetchUser = context.UserMaster.Where(t => t.Email == model.Email).SingleOrDefault();
if (null == fetchUser)
throw new Exception("Not found");
Session["User"] = fetchUser;
I am using the standard Simple Membership model for login via forms in my application. I would like to provide the possibility to login via AD as an alternative.
When logging in via AD, the process should be as follows:
Check that AD authenticates the user, but do not use the information for the principal.
Check if any local user exists with the provided Active Directory username (I have a property on my UserProfile model named ActiveDirectoryID).
If it exists, perform a local login using the local username for this UserProfile.
The problem: I cannot retrieve the local password, so in order to login locally after AD authentication, I need to be able to force the login without the password.
I've considered the following strategies:
Create an extension method for Websecurity to allow Websecurity.Login(string username)
Somehow set the logged in user manually, without implicating Websecurity.
Is this doable / feasible? Is it possible for the framework to create the necessary auth cookie without the plaintext password? And how would I do this?
SOLUTION:
This ended being the correct solution:
public ActionResult ActiveDirectoryLogin(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://DC=MyIntranet,DC=MyCompany,DC=com", model.UserName, model.Password);
object NativeObject = entry.NativeObject;
var internalUser = db.UserProfiles.Where(x => x.ActiveDirectoryID == model.UserName).SingleOrDefault();
if (internalUser != null)
{
FormsAuthentication.SetAuthCookie(internalUser.UserName, model.RememberMe);
return RedirectToLocal(returnUrl);
}
}
catch (DirectoryServicesCOMException)
{
// No user existed with the given credentials
}
catch (InvalidOperationException)
{
// Multiple users existed with the same ActiveDirectoryID in the database. This should never happen!
}
}
return RedirectToAction("Login");
}
This is all that the Websecurity.Login method does:
public static bool Login(string userName, string password, bool persistCookie = false)
{
WebSecurity.VerifyProvider();
bool flag = Membership.ValidateUser(userName, password);
if (flag)
{
FormsAuthentication.SetAuthCookie(userName, persistCookie);
}
return flag;
}
You can write your own method that authenticates against AD and then looks up the user name and the does sets the auth cookie something like:
public static bool MyLogin(string userName, string password, bool persistCookie = false)
{
bool flag = CheckADUser(userName, password);
if (flag)
{
string mappedUsername = GetMappedUser(userName);
if(mappedUsername != "")
{
FormsAuthentication.SetAuthCookie(userName, persistCookie);
}
else
{
flag = false;
}
}
return flag;
}
Hope this helps.