Identity Two-Factor Authentication alternatives - authentication

One of my clients did not want to use any of the standard options (SMS or Email) for 2FA and I was wondering what others have implemented instead.
I felt that the site would be too vulnerable with just a username and password combination, even using max-attempts and timeouts.
A simple option that multiplies up the login uncertainty is by adding an additional security question as part of the login page.
My answer is posted below

Using the code-first approach in creating the user database, I added a set of security questions into my IdentityDbContext class.
public DbSet<SecurityQuestion> SecurityQuestions { get; set; }
This provides a simple list of questions such as "What is your favourite food". the questions should engender reasonably generic answers. The questions are added in the Configuration class Seed method
void AddSecurityQuestion(ApplicationDbContext db, string question)
{
db.SecurityQuestions.Add(new SecurityQuestion() { Question = question });
}
A simple table is sufficient
public class SecurityQuestion
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[StringLength(128)]
[DisplayName("Question")]
public string Question { get; set; }
}
A field as added to the Identity User class. This will contain either null or a hash of a security question and answer. For completeness, a property is added to check if the hash is present. The first time a user logs on, the hash is saved. On subsequent logons, the hash is checked
public string SecurityQuestion { get; protected set; }
[NotMapped]
public bool HasSecurityQuestion
{
get
{
return this.SecurityQuestion != null;
}
}
Hashing uses the same code as the internal Identity methods and stores the seed and hash in the same string
public static string HashSecurityQuestion(string question, string answer)
{
if (question == null)
{
throw new ArgumentNullException("Question is null");
}
if (answer == null)
{
throw new ArgumentNullException("Answer is null");
}
string questionAndAnswer = question + "_" + answer;
// random salt and hash in save result
byte[] salt;
byte[] buffer2;
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(questionAndAnswer, 0x10, 0x3e8))
{
salt = bytes.Salt;
buffer2 = bytes.GetBytes(0x20);
}
byte[] dst = new byte[0x31];
Buffer.BlockCopy(salt, 0, dst, 1, 0x10);
Buffer.BlockCopy(buffer2, 0, dst, 0x11, 0x20);
return Convert.ToBase64String(dst);
}
A verification method is required
public static bool VerifyHashedPassword(string hashedSecurityQuestion, string question, string answer)
{
if (hashedSecurityQuestion == null)
{
return false;
}
if (question == null)
{
throw new ArgumentNullException("Question is null");
}
if (answer == null)
{
throw new ArgumentNullException("Answer is null");
}
string questionAndAnswer = question + "_" + answer;
// has to retrieve salt
byte[] buffer4;
byte[] src = Convert.FromBase64String(hashedSecurityQuestion);
if ((src.Length != 0x31) || (src[0] != 0))
{
return false;
}
byte[] dst = new byte[0x10];
Buffer.BlockCopy(src, 1, dst, 0, 0x10);
byte[] buffer3 = new byte[0x20];
Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20);
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(questionAndAnswer, dst, 0x3e8))
{
buffer4 = bytes.GetBytes(0x20);
}
return buffer3.SequenceEqual(buffer4);
}
In the login process, there is one extra step to verify the security question and answer are checked. The MVC View displays a drop down of questions and a textbox for an answer, both values for which are in the view model
var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: true);
// if the user login is a success, check if a security question exists
if (result == SignInStatus.Success && user.HasSecurityQuestion)
{
// security question exists, so check it
if (!user.VerifySecrityQuestion(model.SecurityQuestion, model.SecurityQuestionAnswer))
{
result = SignInStatus.Failure;
}
}

Authy/Twilio Developer Evangelist here. There are a couple other options for additional security that you have:
Enforce & encourage strong passwords
This includes things like minimum length, showing a password "strength" indicator, and including easy ways for people to use a password manager.
I compiled more details about recommendations for strong passwords:
https://github.com/robinske/betterpasswords
One Time Passwords in the form of TOTP
This is what you'd see with apps like Authy or Google Authenticator. TOTP (time-based one time passwords) is a standard, you can read about that here.
Authy has APIs to implement OTPs here.
Push Authentication
This is another form of 2FA that allows your user to "approve" or "deny" a login request in the form of a push notification. This is the most secure form of 2FA with a seamless user experience, you can read more about how Authy does that here.
Authy has APIs to implement push authentication here.
========
Note: security questions are a lot like additional passwords that can be more easily Googled, so I'd encourage your client to think about using a true second factor.

Related

How to migrate SimpleMembership user data to ASPNET Core Identity

I have an application that I originally built when I was young and foolish and its authentication is set up using the SimpleMembership framework, with all user data contained in a webpages_Membership table. I am very interested in rebuilding my backend as an AspNetCore Web API with AspNetCore Identity via SQL Server, without losing user information.
I've had good luck with coming up with SQL scripts to move everything into an AspNetUsers table in preparation for working with Identity instead of SimpleMembership, but where I'm running into an issue is password hashing. I gather from articles like this and this that my best bet is to override PasswordHasher<IdentityUser> to duplicate the SimpleMembership crypto flow, and then rehash passwords as they come in to gradually migrate the database.
The trouble is I can't find out how to achieve this flow duplication in .NET Core. The latter article linked above states that the SimpleMembership flow is achieved via the System.Web.Helpers.Crypto package, which does not appear to exist in the .NET Core library, and I can't figure out if its implementation is documented anywhere. (Its MSDN documentation says that it is using RFC2898 hashing but I don't know enough about crypto to know if that's enough to go on by itself. This isn't my area of expertise. :( )
Any insight on how to approach this would be much appreciated. Thank you!
For anyone else who may be running into the same trouble -- I was able to find a copy of the System.Web.Helpers.Crypto code somewhere on GitHub, and more or less copied it into a custom password hasher class thus:
public class CustomPasswordHasher : PasswordHasher<IdentityUser>
{
public override PasswordVerificationResult VerifyHashedPassword(IdentityUser user, string hashedPassword,
string providedPassword)
{
var isValidPasswordWithLegacyHash = VerifyHashedPassword(hashedPassword, providedPassword);
return isValidPasswordWithLegacyHash
? PasswordVerificationResult.SuccessRehashNeeded
: base.VerifyHashedPassword(user, hashedPassword, providedPassword);
}
private const int _pbkdf2IterCount = 1000;
private const int _pbkdf2SubkeyLength = 256 / 8;
private const int _saltSize = 128 / 8;
public static bool VerifyHashedPassword(string hashedPassword, string password)
{
//Checks password using legacy hashing from System.Web.Helpers.Crypto
var hashedPasswordBytes = Convert.FromBase64String(hashedPassword);
if (hashedPasswordBytes.Length != (1 + _saltSize + _pbkdf2SubkeyLength) || hashedPasswordBytes[0] != 0x00)
{
return false;
}
var salt = new byte[_saltSize];
Buffer.BlockCopy(hashedPasswordBytes, 1, salt, 0, _saltSize);
var storedSubkey = new byte[_pbkdf2SubkeyLength];
Buffer.BlockCopy(hashedPasswordBytes, 1 + _saltSize, storedSubkey, 0, _pbkdf2SubkeyLength);
byte[] generatedSubkey;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, _pbkdf2IterCount))
{
generatedSubkey = deriveBytes.GetBytes(_pbkdf2SubkeyLength);
}
return ByteArraysEqual(storedSubkey, generatedSubkey);
}
internal static string BinaryToHex(byte[] data)
{
var hex = new char[data.Length * 2];
for (var iter = 0; iter < data.Length; iter++)
{
var hexChar = (byte) (data[iter] >> 4);
hex[iter * 2] = (char) (hexChar > 9 ? hexChar + 0x37 : hexChar + 0x30);
hexChar = (byte) (data[iter] & 0xF);
hex[iter * 2 + 1] = (char) (hexChar > 9 ? hexChar + 0x37 : hexChar + 0x30);
}
return new string(hex);
}
[MethodImpl(MethodImplOptions.NoOptimization)]
private static bool ByteArraysEqual(byte[] a, byte[] b)
{
if (ReferenceEquals(a, b))
{
return true;
}
if (a == null || b == null || a.Length != b.Length)
{
return false;
}
var areSame = true;
for (var i = 0; i < a.Length; i++)
{
areSame &= (a[i] == b[i]);
}
return areSame;
}
}
This class overrides VerifyHashedPassword and checks whether the user's provided password works with the old Crypto hashing; if so, the method returns PasswordVerificationResult.SuccessRehashNeeded. Otherwise, it passes the password off to the base class's method and verifies it as normal with the .NET Core hashing behavior.
You can then instruct your UserManager to use this password hasher instead of the default by including it in your dependency injection configuration in Startup.cs:
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IPasswordHasher<IdentityUser>, CustomPasswordHasher>();
}
...
}
My eventual intention is to have my controller trigger a rehash of the user's password when that SuccessRehashNeeded result is returned, allowing a gradual migration of all users to the correct hashing schema.

Using Roles with Forms Authentication

I'm using forms authentication in my MVC application. This is working fine. But not I want to adjust authorization to only allow people in certain roles. The logins correspond to users in active directory and the roles correspond to the groups the users are in.
For authentication, I simply call FormsAuthentication.SetAuthCookie(username, true) after verifying the login.
For authorizing, I first applied the attribute to the controllers I want to secure
[Authorize(Roles = "AllowedUsers")]
public class MyController
...
Next, I'm handling the OnAuthenticate event in global.asax.
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(
Request.Cookies[FormsAuthentication.FormsCookieName].Value);
// Create WindowsPrincipal from username. This adds active directory
// group memberships as roles to the user.
args.User = new WindowsPrincipal(new WindowsIdentity(ticket.Name));
FormsAuthentication.SetAuthCookie(ticket.Name, true);
}
catch (Exception e)
{
// Decrypt method failed.
}
}
}
else
{
throw new HttpException("Cookieless Forms Authentication is not " + "supported for this application.");
}
}
With this when someone accesses the website they get the login screen. From there they can actually log in. However, somehow it doesn't save the auth cookie and they get a login screen after the next link they click. I tried adding a call to SetAuthCookie() in OnAuthenticate() but they made no difference.
Before I added this event handler to handle authorization, authentication worked fine. So somewhere in the framework User is being set. I'm wondering if this the correct approach and I'm just missing something or if I need a different approach.
What do I need to do to get this to work?
Thanks,
Scott
It seems like my initial approach won't work. I was trying to get ASP.NET to automatically load user roles from their AD account. No comment was given on whether this was possible. However, the research I've done indicates I'll have to write code to load AD group memberships into user roles.
The solution to creating the user principal that ASP.NET MVC uses appears to be to create it in FormsAuthentication_OnAuthenticate() and assign it to Context.User. It appears if I don't set Context.User ASP.NET MVC creates a user principal based off the auth ticket after FormsAuthentication_OnAuthenticate() returns. Additionally, ASP.NET MVC appears to do nothing with Context.User if I set it in FormsAuthentication_OnAuthenticate().
The following is what I ended up doing.
This is the code that handles authentication
public ActionResult LogOn(FormCollection collection, string returnUrl)
{
// Code that authenticates user against active directory
if (authenticated)
{
var authTicket = new FormsAuthenticationTicket(username, true, 20);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
authCookie.Expires = DateTime.Now.AddMinutes(30);
Response.Cookies.Add(authCookie);
if (Url.IsLocalUrl(returnUrl)
&& returnUrl.Length > 1
&& returnUrl.StartsWith("/", StringComparison.OrdinalIgnoreCase)
&& !returnUrl.StartsWith("//", StringComparison.OrdinalIgnoreCase)
&& !returnUrl.StartsWith("/\\", StringComparison.OrdinalIgnoreCase))
{
return Redirect(returnUrl);
}
else
{
return Redirect("~/");
}
}
return View();
}
I initially tried just calling FormsAuthentication.SetAuthCookie(username, true) instead of manually creating, encrypting, and adding it to the Response cookie collections. That worked in the development environment. However, it didn't after I published to the website.
This is the log off code
public ActionResult LogOff()
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
authCookie.Expires = DateTime.Today.AddDays(-1);
}
Response.Cookies.Add(authCookie);
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
FormsAuthentication.SignOut() doesn't seem to do anything after I switched to manually creating, encrypting, and adding the auth ticket to the response cookie collection in the logon code. So I had to manually expire the cookie.
This is the code I have for FormsAuthentication_OnAuthenticate()
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs args)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || string.IsNullOrWhiteSpace(authCookie.Value))
return;
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
UserData userData = null;
if (Application["UserData_" + authTicket.Name] == null)
{
userData = new UserData(authTicket.Name);
Application["UserData_" + authTicket.Name] = userData;
}
else
{
userData = (UserData)Application["UserData_" + authTicket.Name];
}
Context.User = new GenericPrincipal(new GenericIdentity(authTicket.Name), userData.Roles);
}
UserData is a class I created to handle caching user roles. This was needed because of the time it takes for active directory to return the group memberships the user belongs to. For completeness, the following is the code I have for UserData.
public class UserData
{
private int _TimeoutInMinutes;
private string[] _Roles = null;
public string UserName { get; private set; }
public DateTime Expires { get; private set; }
public bool Expired { get { return Expires < DateTime.Now; } }
public string[] Roles
{
get
{
if (Expired || _Roles == null)
{
_Roles = GetADContainingGroups(UserName).ToArray();
Expires = DateTime.Now.AddMinutes(_TimeoutInMinutes);
}
return _Roles;
}
}
public UserData(string userName, int timeoutInMinutes = 20)
{
UserName = userName;
_TimeoutInMinutes = timeoutInMinutes;
}
}
Roles can also be stored in a cookie and you have at least two options:
a role provider cookie (another cookie that supports the forms cookie), set with cacheRolesInCookie="true" on a role provider config in web.config. Roles are read the first time authorization module asks for roles and the cookie is issued then
a custom role provider that stores roles in the userdata section of the forms cookie, roles have to be added to the user data section of the forms cookie manually
The Authorization module asks the current principal for user roles, which, if role provider is enabled, either scans the role cookie (the first option) or fires the custom role provider methods.
Yet another, recommended approach is to switch to the Session Authentication Module (SAM) that can replace forms authentication. There are important pros, including the fact that SAM recreates ClaimsPrincipal out of the cookie and roles are just Role claims:
// create cookie
SessionAuthenticationModule sam =
(SessionAuthenticationModule)
this.Context.ApplicationInstance.Modules["SessionAuthenticationModule"];
ClaimsPrincipal principal =
new ClaimsPrincipal( new GenericPrincipal( new GenericIdentity( "username" ), null ) );
// create any userdata you want. by creating custom types of claims you can have
// an arbitrary number of your own types of custom data
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.Role, "role1" ) );
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.Role, "role2" ) );
var token =
sam.CreateSessionSecurityToken(
principal, null, DateTime.Now, DateTime.Now.AddMinutes( 20 ), false );
sam.WriteSessionTokenToCookie( token );
From now on, the identity is stored in a cookie and managed automatically and, yes, the Authorization attribute on your controllers works as expected.
Read more on replacing forms module with SAM on my blog:
http://www.wiktorzychla.com/2012/09/forms-authentication-revisited.html

How can I set (override) all items in hash

I want to set all entries in Hash. (SetAllEntriesToHash)
It must Clear all items in hash before running.
It is opposite of GetAllEntriesFromHash.
You have a couple options here.
1) You could let ServiceStack take care of this for you by using the high level Redis API.
public class Poco
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
...
// Client
var client = new RedisClient("localhost", 6379);
// This will store the object for you in a Redis hash.
client.StoreAsHash(new Poco { Id = 1, Name = "Test Name", Description = "Test Description" });
// This will fetch it back for you.
var result = client.GetFromHash<Poco>(1);
This approach will disconnect you from having to deal directly with the hashing particulars. ServiceStack will figure out everything for you and stuff the object you send it into a hash automatically. If you want to update that object, just send it a new one with the same ID.
The flip-side of this is that you're giving up control of how your data is stored in Redis for an easier programming experience.
2) You handle all of the stuff yourself. There is no SetAllEntriesToHash function pre-built.
// Client
var client = new RedisClient("localhost", 6379);
// Clear all existing keys
var keysToClear = new Dictionary<string,string>();
client.GetHashKeys("xxxxx").ForEach(k => keysToClear.Add(k, ""));
client.SetRangeInHash("xxxxx", keysToClear);
// Save new key/values.
client.SetRangeInHash("xxxxx", new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("1", "value 1"),
new KeyValuePair<string, string>("2", "value 2"),
new KeyValuePair<string, string>("3", "value 3"),
});
Alternatively, it may be easier just to delete and recreate the hash.
I would also like to draw your attention to RedisNativeClient. It allows you to run Redis commands that directly map to http://redis.io/commands.

Custom OpenIdClient for Customer URL in MVC 4

I'm working with the default template for MVC 4 and trying to add my own openID provider for example http://steamcommunity.com/dev to the list of openID logins and an openID box where the user can type in their openID information.
To add Google I just un-comment
OAuthWebSecurity.RegisterGoogleClient();
as for other custom solutions you can do something like
OAuthWebSecurity.RegisterClient(new SteamClient(),"Steam",null);
The trouble I have is creating SteamClient (or a generic one) http://blogs.msdn.com/b/webdev/archive/2012/08/23/plugging-custom-oauth-openid-providers.aspx doesn't show anywhere to change the URL.
I think the reason I could not find the answer is that most people thought it was common sense. I prefer my sense to be uncommon.
public class OidCustomClient : OpenIdClient
{
public OidCustomClient() : base("Oid", "http://localhost:5004/") { }
}
Based on #Jeff's answer I created a class to handle Stack Exchange OpenID.
Register:
OAuthWebSecurity.RegisterClient(new StackExchangeOpenID());
Class:
public class StackExchangeOpenID : OpenIdClient
{
public StackExchangeOpenID()
: base("stackexchange", "https://openid.stackexchange.com")
{
}
protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response)
{
FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
if (fetchResponse != null)
{
var extraData = new Dictionary<string, string>();
extraData.Add("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
extraData.Add("name", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName));
return extraData;
}
return null;
}
protected override void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
{
var fetchRequest = new FetchRequest();
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.FullName);
request.AddExtension(fetchRequest);
}
}
Retrieving extra data:
var result = OAuthWebSecurity.VerifyAuthentication();
result.ExtraData["email"];
result.ExtraData["name"];

Generate test data in Raven DB

I am looking for a preferred and maintainable way of test data generation in Raven DB. Currently, our team does have a way to do it through .NET code. Example is provided.
However, i am looking for different options. Please share.
public void Execute()
{
using (var documentStore = new DocumentStore { ConnectionStringName = "RavenDb" })
{
documentStore.Conventions.DefaultQueryingConsistency = ConsistencyOptions.QueryYourWrites;
// Override the default key prefix generation strategy of Pascal case to lower case.
documentStore.Conventions.FindTypeTagName = type => DocumentConvention.DefaultTypeTagName(type).ToLower();
documentStore.Initialize();
InitializeData(documentStore);
}
}
Edit: Raven-overflow is really helpful. Thanks for pointing out to the right place.
Try checking out RavenOverflow. In there, I've got a FakeData project that has fake data (both hardcoded AND randomly generated). This can then be used in either my Tests project or the Main Website :)
Here's some sample code...
if (isDataToBeSeeded)
{
HelperUtilities.CreateSeedData(documentStore);
}
....
public static void CreateSeedData(IDocumentStore documentStore)
{
Condition.Requires(documentStore).IsNotNull();
using (IDocumentSession documentSession = documentStore.OpenSession())
{
// First, check to make sure we don't have any data.
var user = documentSession.Load<User>(1);
if (user != null)
{
// ooOooo! we have a user, so it's assumed we actually have some seeded data.
return;
}
// We have no users, so it's assumed we therefore have no data at all.
// So let's fake some up :)
// Users.
ICollection<User> users = FakeUsers.CreateFakeUsers(50);
StoreFakeEntities(users, documentSession);
// Questions.
ICollection<Question> questions = FakeQuestions.CreateFakeQuestions(users.Select(x => x.Id).ToList());
StoreFakeEntities(questions, documentSession);
documentSession.SaveChanges();
// Make sure all our indexes are not stale.
documentStore.WaitForStaleIndexesToComplete();
}
}
....
public static ICollection<Question> CreateFakeQuestions(IList<string> userIds, int numberOfFakeQuestions)
{
.... you get the idea .....
}