ASP.NET Identity 2.0 was released last month and it's got a number of significant updates and new features that are worth checking out. For historical context, read the "Introduction to ASP.NET Identity" article that includes a lot of background and information on why certain decisions were made, as well as an overview of some of the goals of ASP.NET Identity 2.0 like:
- One Identity system for ASP.NET Web Forms, MVC, Web API, and Web Pages
- Total control over user profile schema.
- Pluggable storage mechanisms from Windows Azure Storage Table Service to NoSQL databases
- Unit Testable
- Claims-based Auth adds more choice over simple role membership
- Social Logins (MSFT, FB, Google, Twitter, etc)
- Based on OWIN middleware, ASP.NET Identity has no System.Web dependency
You can watch a video of Pranav Rastogi and I upgrading the ASP.NET Membership systems on an older ASP.NET application to the latest bits. There's also migration docs in detail:
- Migrating an Existing Website from SQL Membership to ASP.NET Identity
- Migrating Universal Provider Data for Membership and User Profiles to ASP.NET Identity
ASP.NET Identity is on CodePlex today (and soon to be open sourced...paperwork) at https://aspnetidentity.codeplex.com/ or access the NuGet feed for nightly builds.
Adding Two-Factor authentication to an ASP.NET application
I recently changed all my accounts online to two-factor auth, and I really recommend you do as well. Here's how to add Two-Factor Auth to an ASP.NET application using Identity 2.0.
You'll have a class that is a UserManager that handles access to users and how they are stored. Inside this manager there's an IIdentityMessageService that you can implement to validate a user with whatever you want, like email, SMS, or a time-based token.
Here's an example SmsService where I'm using Twilio to send text messages. Again, you can do whatever you want in your implementation.
public class SmsService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
// Plug in your sms service here to send a text message.
message.Destination = Keys.ToPhone; //your number here
var twilio = new TwilioRestClient(Keys.TwilioSid, Keys.TwilioToken);
var result = twilio.SendMessage(Keys.FromPhone, message.Destination, message.Body);
return Task.FromResult(0);
}
}
If I were sending an EmailMessage, I'd do something like this. Note it's just another implementation of the same simple interface:
public class EmailService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
string text = message.Body;
string html = message.Body;
//do whatever you want to the message
MailMessage msg = new MailMessage();
msg.From = new MailAddress("scott@hanselman.com");
msg.To.Add(new MailAddress(message.Destination));
msg.Subject = message.Subject;
msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(text, null, MediaTypeNames.Text.Plain));
msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html));
SmtpClient smtpClient = new SmtpClient("smtp.whatever.net", Convert.ToInt32(587));
System.Net.NetworkCredential credentials = new System.Net.NetworkCredential(Keys.EmailUser, Keys.EMailKey);
smtpClient.Credentials = credentials;
smtpClient.Send(msg);
return Task.FromResult(0);
}
}
In your IdentityConfig.cs you can register as many TwoFactorProviders as you'd like. I'm adding both Email and Sms here. They include token providers but again, everything is pluggable.
manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider{
MessageFormat = "Your security code is: {0}"
});
manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider{
Subject = "SecurityCode",
BodyFormat = "Your security code is {0}"
});
manager.EmailService = new EmailService();
manager.SmsService = new SmsService();
If a user tries to login you need to make sure they are a VerifiedUser. If not, get a valid two factor provider and send them a code to validate. In this case, since there are two providers to choice from, I let them pick from a dropdown. Here's the POST to /Account/SendCode:
public async TaskSendCode(SendCodeViewModel model)
{
// Generate the token and send it
if (!ModelState.IsValid)
{
return View();
}
if (!await SignInHelper.SendTwoFactorCode(model.SelectedProvider))
{
return View("Error");
}
return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl });
}
The sender of the two factor code depends on your implementation, of course.
public async TaskSendTwoFactorCode(string provider)
{
var userId = await GetVerifiedUserIdAsync();
if (userId == null)
{
return false;
}
var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider);
// See IdentityConfig.cs to plug in Email/SMS services to actually send the code
await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token);
return true;
}
When it's time to get the code from them, they need to have logged in with name and password already, and we're now checking the code:
[AllowAnonymous]
public async TaskVerifyCode(string provider, string returnUrl)
{
// Require that the user has already logged in via username/password or external login
if (!await SignInHelper.HasBeenVerified())
{
return View("Error");
}
var user = await UserManager.FindByIdAsync(await SignInHelper.GetVerifiedUserIdAsync());
return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl });
}
We can sign users potentially a number of ways, like with External Sign Ins (Twitter, etc) but here's the TwoFactorSignIn
public async TaskTwoFactorSignIn(string provider, string code, bool isPersistent, bool rememberBrowser)
{
var userId = await GetVerifiedUserIdAsync();
if (userId == null)
{
return SignInStatus.Failure;
}
var user = await UserManager.FindByIdAsync(userId);
if (user == null)
{
return SignInStatus.Failure;
}
if (await UserManager.IsLockedOutAsync(user.Id))
{
return SignInStatus.LockedOut;
}
if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code))
{
// When token is verified correctly, clear the access failed count used for lockout
await UserManager.ResetAccessFailedCountAsync(user.Id);
await SignInAsync(user, isPersistent, rememberBrowser);
return SignInStatus.Success;
}
// If the token is incorrect, record the failure which also may cause the user to be locked out
await UserManager.AccessFailedAsync(user.Id);
return SignInStatus.Failure;
}
If you want this blog post's sample code, make an EMPTY ASP.NET Web Application and run this NuGet command from the Package Manager Console
Install-Package Microsoft.AspNet.Identity.Samples -Pre
Have fun!
Related Links
- Adding ASP.NET Identity to an Empty or Existing Web Forms Project
- Implementing a Custom MySQL ASP.NET Identity Storage Provider
- Create an ASP.NET MVC 5 App with Facebook and Google OAuth2 and OpenID Sign-on
The tutorial uses the ASP.NET Identity API to add profile information to the user database, and how to authenticate with Google and Facebook. - Deploy a Secure ASP.NET MVC app with Membership, OAuth, and SQL Database to a Windows Azure Web Site
This tutorial shows how to use the Identity API to add users and roles. - Individual User Accounts in Creating ASP.NET Web Projects in Visual Studio 2013
- Organizational Accounts in Creating ASP.NET Web Projects in Visual Studio 2013
- Customizing profile information in ASP.NET Identity in VS 2013 templates
- Get more information from Social providers used in the VS 2013 project templates
* Photo of German Lorenz cipher machine by Timitrius used under CC Attribution
Sponsor: Big thanks to Novalys for sponsoring the blog feed this week! Check out their security solution that combines authentication and user permissions. Secure access to features and data in most applications & architectures (.NET, Java, C++, SaaS, Web SSO, Cloud...). Try Visual Guard for FREE.
© 2014 Scott Hanselman. All rights reserved.