Adding Two-Factor authentication to an ASP.NET application
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<ApplicationUser> {
MessageFormat = "Your security code is: {0}"
});
manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser> {
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 Task<ActionResult> SendCode(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 Task<bool> SendTwoFactorCode(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 Task<ActionResult> VerifyCode(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 Task<SignInStatus> TwoFactorSignIn(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.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
- ASP.NET Identity is not completely an OSS right now as the ASP.NET Identity framework code is not public
- With ASP.NET Identity, it's super slick and easy to replace the data storage option. For example, I have written the RavenDB port for ASP.NET Identity and it supports v2.0.0 (as pre-release)
I'm not sure if that means we're behind the curve, but it's very handy :)
P.S. 'watch a video of Pranav Rastogi and I' - surely not... http://www.oxforddictionaries.com/words/i-or-me
I came across an error while installing the nuget package and just thought I'd share. The following error is displayed when the install attempts to add EntityFramework 6.1:
Install failed. Rolling back...
Install-Package : Input string was not in a correct format.
At line:1 char:16
+ Install-Package <<<< Microsoft.AspNet.Identity.Samples -Pre
+ CategoryInfo : NotSpecified: (:) [Install-Package], FormatException
+ FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PowerShell.Commands.InstallPackageCommand
This post on Codeplex helped resolve the issue. It seems as though it may be caused by having multiple versions of Visual Studio installed.
Hope this helps.
https://github.com/WillSullivan/AzureTableIdentityStorageProvider
I'm the only one currently using this, afaict, so I'm beating down bugs as they crop up.
All in all I'm happy, I look forward to lockout and two-factor auth support now.
I am planning to change my existing application to use ASPNetOneIdentity. Is it possible to call my exisitng repository class methods like UserRepository.Add from UserManager?
I would like to call like below, with out using EF, Async, await or Task
var user = new ApplicationUser() { UserName = model.UserName };
var result = UserManager.Create(user, model.Password);
Is it possible to customize Asp.Net one identity?
So that when we add the CustomerController using default "MVC 5 Controller with views, using Entity Framework" scaffolding template should create a drop-down of users from AspNetUsers table.
(Refer stackoverflow.com/.../3542245 for more details related to question)
Please you can make 2 factor authentication via google a asp.net script for the public they can download and modified & explain how they can change his google id and password .
its very help full for public like me.
I recently upgraded my SPA to use ASP.NET Identity 2.0 with Two-factor authentication and had no problems at all.
Ofcourse it depends on the implementation of your SPA client when it comes to clientside routing, but it's pretty easy to get everything working.
https://github.com/KriaSoft/AspNet.Identity
In the login process whether I use a 3rd party provider or register within the site I don't see where a user has the option to choose a username like they did with the simple membership.
Can you point me in the direction of some out of the box username info?
thank you.
https://danieleagle.com/blog/2014/05/setting-up-asp-net-identity-framework-2-0-with-database-first-vs2013-update-2-spa-template/
My guess is tweeking IdentityConfig.cs?
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
Your sample source code really helped me out today.
It also helped me determine that I needed to get the prerelease version of Microsoft.AspNet.Identity.Owin to use the SignInManager and enable the logic for determining if the 2 factor authentication code had been verified.
Cheers Mr H
To change your app to use username instead of email, you can change the ExternalLoginConfirmation and change the viewmodel to use username and pull in the username field in the controller action.
you can look at the following question on StackOverflow which explains how to do this http://stackoverflow.com/questions/20372594/dependency-injection-structuremap-asp-net-identity-mvc-5
you can look at the following question on StackOverflow which explains how to do this http://stackoverflow.com/questions/20372594/dependency-injection-structuremap-asp-net-identity-mvc-5
This is totally an application flow on how do you add a phone number.
Comments are closed.