I'm writing an application which needs to have personally identifiable information removed/absent at all times from the database. Given that someone may use their real name in their username, and that an email address may be present in their AspUserIdentity records, I have decided one solution might be to hash these values. In simple terms: when someone logs in with a username, I hash the username they entered and see if that hash exists in the database; if it does, then I log them in. This is easy to do and works just fine by modifying the Login and Register methods in the AccountController. But now I am left with no knowledge of the entered username...
I could just store the username in session, but that seems jankety. What I'd like to do is to update the cookie that gets sent down upon successful login to use the username they entered (and not the hashed value stored in the DB). That way User.Identity.GetUserName() returns the plain text username (and not the hashed username). To the client the process ought to be transparent (and to me as the programmer too).
The question is: how? What's the best place to do this? I'm still relatively green when it comes to the latest ASP.NET Identity stuff. I see in Startup.Auth there's a lot of juicy stuff related to cookies, but I don't see anywhere I can modify the cookie itself upon login and prior to it being sent down.
Is all of this deep within Owin itself?
Thanks in advance,
When user logs in and you compare the hash of username, you can add their real username as a claim to the identity. This is serialised into cookie and available with the user on every request, but not persisted in a DB:
public async Task SignIn(string userName, string password, bool rememberMe)
{
var hashedUsername = getMyHash(username)
var loggedInUser = await userManager.FindAsync(hashedUsername, password);
if (loggedInUser == null)
{
// failed to login
return FailedToLogin(); // whatever you do there
}
// Ok, from now on we have user who provided correct username and password.
// and because correct username/password was given, we reset count for incorrect logins. This is for user lockout
await userManager.ResetAccessFailedCountAsync(loggedInUser.Id);
if (!loggedInUser.EmailConfirmed)
{
return EmailIsNotConfirmed(); // email is not confirmed - display a message
}
if (await userManager.IsLockedOutAsync(loggedInUser.Id))
{
return UserLockedOut(); // user is locked out - display a message
}
var identity = await userManager.CreateIdentityAsync(loggedInUser);
identity.AddClaim(new Claim("OriginalUsername", originalUsername));
var authenticationManager = context.GetOwinContext().Authentication;
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = rememberMe }, identity);
//TODO redirect to a home page
}
Then when you need to display an actual username, not a hash do this:
public static String GetOriginalUsername(this IPrincipal principal)
{
if (principal == null)
{
return String.Empty;
}
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
return String.Empty;
}
var originalUsernameClaim = principal.Claims.SingleOrDefault(c => c.Type == "OriginalUsername");
if (originalUsernameClaim == null)
{
return String.Empty;
}
return originalUsernameClaim.Value;
}
And call this method on User.GetOriginalUsername() in *.cshtml files or in Controllers. Or HttpContext.Current.User.GetOriginalUsername() if you need it somewhere else.
Related
I'm trying to launch a really basic chat app for myself and a few friends and wanted to include a password check upon loading the site by using a prompt + socket.emit:
var username = prompt('password:');
socket.emit('password', password);
I wanted to insert a function that checks their input against whatever I define as the answer. Something like this:
var codeword = prompt('password:', function checkPrompt(password) {
if (prompt.password.length === 0) {
return false;
}
if (password.value !== answer.value) {
return false;
}
return true;
});
But I get ERR_INVALID_ARG_TYPE(name, 'Function', value); once I run the app. How can I implement this?
Sidenote: I know this is super insecure. I'll implement a real password check once I have a DB set up but until then I just wanted a simple placeholder.
prompt is a "blocking" method, so there is no need for a callback. Callbacks are needed when you are uncertain when code within the callback will be executed. This is not the case here. You can work with the returned result in a do...while loop for example.
Dummy example with static password check:
// We will keep the user in the loop until the password is correct...
do {
var password = prompt('Password:');
} while (password !== 'abcd');
// At this point we know the password was correct.
alert('Password was correct!');
Dummy example with dedicated auth method:
function authenticate(username, password) {
if (username == 'john' && password == 'abcd') {
return true;
}
return false;
}
// We will keep the user in the loop until the password is correct...
do {
var username = prompt('Username:');
var password = prompt('Password:');
var loginSuccessful = authenticate(username, password);
} while (!loginSuccessful);
// At this point we know the password was correct.
alert('Password was correct!');
From the security point of view, it only makes sense to have the authentication logic on server-side, so you'll most probably will want to make an AJAX call from within the above authenticate function (using the Fetch API for example or jQuery.post - if you're using jQuery). Then the method would return true or false based on the response of the server-side call.
For one of our customers, we created a custom credential provider which receives a decryption key and the filename of an encrypted file which container the username and password. This mechanism works perfectly for local user accounts. The user is authenticated when needed, and the old password is no longer accepted right after the user changes his password.
However, for windows live accounts the user can sometimes login using his old password after changing his password online (accounts.microsoft.com) and even after logging in to windows with the newly created password. Strange thing is, that the user cannot login by typing his old password. It only works when using the credential provider.
To make it more confusing, sometimes it works as expected and the behavior seems to differ from machine to machine.
My gut feeling tells me, there is something wrong with the code we use to authenticate the user, but I cannot figure out what is going wrong. We already tried to set the OldPasswordAllowedPeriod registry value, but this seems not to work.
We use the following GetSerialization() implementation, to fill the authentication buffer:
public int GetSerialization(...)
{
pcpgsr = _CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE.CPGSR_NO_CREDENTIAL_NOT_FINISHED;
pcpcs = new _CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION();
ppszOptionalStatusText = string.Empty;
pcpsiOptionalStatusIcon = _CREDENTIAL_PROVIDER_STATUS_ICON.CPSI_NONE;
try
{
var inCredSize = 0;
var inCredBuffer = Marshal.AllocCoTaskMem(0);
if (string.IsNullOrEmpty(_username) || _password == null || _password.Length == 0)
{
return SetAuthenticationError(out pcpgsr, out pcpsiOptionalStatusIcon, out ppszOptionalStatusText, "This NFC card has not been registered on this screen.");
}
if (!PInvoke.CredPackAuthenticationBuffer(0, _username, SecureStringToString(_password), inCredBuffer, ref inCredSize))
{
Marshal.FreeCoTaskMem(inCredBuffer);
inCredBuffer = Marshal.AllocCoTaskMem(inCredSize);
if (PInvoke.CredPackAuthenticationBuffer(0, _username, SecureStringToString(_password), inCredBuffer, ref inCredSize))
{
pcpgsr = _CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE.CPGSR_RETURN_CREDENTIAL_FINISHED;
pcpsiOptionalStatusIcon = _CREDENTIAL_PROVIDER_STATUS_ICON.CPSI_SUCCESS;
pcpcs.clsidCredentialProvider = Guid.Parse(Constants.CredentialProviderUID);
pcpcs.rgbSerialization = inCredBuffer;
pcpcs.cbSerialization = (uint)inCredSize;
RetrieveNegotiateAuthPackage(out var authPackage);
pcpcs.ulAuthenticationPackage = authPackage;
return HResult.S_OK;
}
_logger.LogError($"Failed to pack credentials for: {_username}.");
return SetAuthenticationError(out pcpgsr, out pcpsiOptionalStatusIcon, out ppszOptionalStatusText, "Failed to pack credentials.");
}
_logger.LogWarning("GetSerialization unexpectedly preliminary succesfully buffered credentials");
return SetAuthenticationError(out pcpgsr, out pcpsiOptionalStatusIcon, out ppszOptionalStatusText, "Something unexpected went wrong!");
}
catch (Exception ex)
{
// In case of any error, do not bring down winlogon
_logger.LogError(ex);
return SetAuthenticationError(out pcpgsr, out pcpsiOptionalStatusIcon, out ppszOptionalStatusText, "Something unexpected went wrong!");
}
finally
{
_shouldAutoLogin = false; // Block auto-login from going full-retard
}
}
Can someone point me in the right direction to solve this issue? Or, has someone any idea on what we are doing wrong when authenticating the user, using our custom credential provider?
Thanks in advance!
I'm creating two projects (MVC 5 and Web API) using ASP.Net Identity 2.1 and I couldn't find how to use both email and username for authentication (an area called Admin must use a username and the common area must use email addresses for authentication).
The problem is that there is only one method for authentication and it does not allow you to specify if you will compare with the email address or the username.
SignInHelper.PasswordSignIn
What should I do to achieve this?
SignInManager will not you help with it, you'll need to use UserManager and a bit more jiggery-pokery (that's technical term!):
This is what I have for this scenario:
var unauthUserByUsername = await userManager.FindByNameAsync(command.UserName);
var unauthUserByEmail = await userManager.FindByEmailAsync(command.UserName);
var unauthenticatedUser = unauthUserByUsername ?? unauthUserByEmail;
if (unauthenticatedUser == null)
{
logger.Warn("User {0} is trying to login but username is not correct", command.UserName);
return View(); // stop processing
}
var loggedInUser = await userManager.FindAsync(unauthenticatedUser.UserName, command.Password);
if (loggedInUser == null)
{
// username is correct, but password is not correct
logger.Warn("User {0} is trying to login with incorrect password", command.UserName);
await userManager.AccessFailedAsync(unauthenticatedUser.Id);
return View(); // stop processing
}
// Ok, from now on we have user who provided correct username and password.
// and because correct username/password was given, we reset count for incorrect logins.
await userManager.ResetAccessFailedCountAsync(loggedInUser.Id);
if (!loggedInUser.EmailConfirmed)
{
logger.Warn("User {0} is trying to login, entering correct login details, but email is not confirmed yet.", command.UserName);
return View("Please confirm your email"); // stop processing
}
if (await userManager.IsLockedOutAsync(loggedInUser.Id))
{
// when user is locked, but provide correct credentials, show them the lockout message
logger.Warn("User {0} is locked out and trying to login", command.UserName);
return View("Your account is locked");
}
logger.Info("User {0} is logged in", loggedInUser.UserName);
// actually sign-in.
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
await userManager.SignInAsync(authenticationManager, loggedInUser, false);
This checks if user has confirmed email, if user is locked out and does lock user out after a certain number of attempts (given all other settings for locking-out are enabled).
This way both are allowed
var userEmail = await UserManager.FindByEmailAsync(model.Login);
if (userEmail == null)
{
var user = await UserManager.FindByNameAsync(model.Login);
if (user == null)
{
model.Login = "";
}
}
else
{
model.Login = userEmail.UserName;
}
var result = await SignInManager.PasswordSignInAsync(model.Login, model.Password, model.RememberMe, shouldLockout: false);
I'm looking to send the user an SMS when reseting their password. I already have the facilities to send a SMS, I just need a guide on how to set it up with Identity 2.0. I can't seem to find any useful info online, the reference code itself isn't properly commented either.
I want to generate a security code, send it to the user, he must then input it into a form and then be allowed to reset his/her password. Can anyone direct me to a guide/tutorial that explains this process?
After digging in the identity source code i found an alternative token provider that can generate tokens similar to phone number confirmation (six digits).
I had to implement two methods in my UserManager to generate the code and then to validate it.
I declared the token provider inside the UserManager
private TotpSecurityStampBasedTokenProvider<User, string> smsResetTokenProvider = new TotpSecurityStampBasedTokenProvider<User, string>();
This is the first method to generate the code:
public async Task<string> GenerateSMSPasswordResetToken(string userId)
{
var user = await base.FindByIdAsync(userId);
var token = await smsResetTokenProvider.GenerateAsync("Reset Password", this, user);
return token;
}
This is the second method to validate the code:
public async Task<IdentityResult> SMSPasswordResetAsync(string userId, string token, string newPassword)
{
var user = await base.FindByIdAsync(userId);
var valid = await smsResetTokenProvider.ValidateAsync("Reset Password", token, this, user);
if (valid)
{
var passwordStore = Store as IUserPasswordStore<User, string>;
var result = await UpdatePassword(passwordStore, user, newPassword);
if (!result.Succeeded)
{
return result;
}
return await UpdateAsync(user);
}
else
{
return IdentityResult.Failed("InvalidToken");
}
}
You may need to tweak the code depending on your user manager
I am evaluating ServiceStack using OrmLite. The built in Auth service, along with Session and Cache are so much better than ASP.NET membership provider.
However, out of the box the Auth Service does not provide some of the features required for apps we want to build like:
Change password
Locking of account after 3 unsuccessful logon attempts
Disabling user accounts
Password reminder question and answer
Audit log of log on attempts
Do I need to build custom auth provider or is there something out there which already does provides this functionality?
Many thanks!
I'm just starting to implement a password reset and can see two ways of achieving it (I've not tested - or even tried - either yet):
1.Create a class that inherits from Registration and handles PUT. It should then be possible to call the UpdateUserAuth method of the registration class which would change the password. The problem - for me - here is that the put validation requires username and password to be specified, not just one (We only use email as an identifier). This could be worked around by turning the validation feature off.
2.Create a password reset service that does what UpdateUserAuth does.
var session = this.GetSession();
var existingUser = UserAuthRepo.GetUserAuth(session, null);
if (existingUser == null)
{
throw HttpError.NotFound("User does not exist");
}
var newUserAuth = ToUserAuth(request);
UserAuthRepo.UpdateUserAuth(newUserAuth, existingUser, request.Password);
Obviously need to add some appropriate validation in.
UPDATED
I've put my change password reminder/reset service up as a gist (My first gist!)
here's what I did, works well. - I realise the "new" is a code-smell, just inject it :)
private int LoginAttempts = 0;
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var authRepo = authService.TryResolve<IUserAuthRepository>();
if (authRepo == null)
{
Log.WarnFormat("Tried to authenticate without a registered IUserAuthRepository");
return false;
}
var session = authService.GetSession();
UserAuth userAuth = null;
if (authRepo.TryAuthenticate(userName, password, out userAuth))
{
session.PopulateWith(userAuth);
session.IsAuthenticated = true;
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
session.ProviderOAuthAccess = authRepo.GetUserOAuthProviders(session.UserAuthId)
.ConvertAll(x => (IOAuthTokens)x);
return true;
}
else
{
LoginAttempts++;
if (LoginAttempts >= 3)
{
ServiceStack.ServiceInterface.Service s = new Service();
s.Db.ExecuteSql("update [User] set AccountLocked = 'true' where Email='" + userName + "'");
}
authService.RemoveSession();
return false;
}
}
and I hope the mod_from_hell manages to leave this alone!!!