Last week I was looking on how to enable Two Factor Authentication in a RESTful ASP.NET Web API service using Soft Tokens not SMS. Most of the examples out there show how to implement this in MVC application where there will be some cookies transmitted between requests, this approach defeats the stateless nature of the RESTful APIs, as well most of the examples ask for the passcode on the login form only, so there was no complete example on how to implement TFA in RESTful API.
The live AngularJS demo application is hosted on Azure, the source code for this tutorial on GitHub.
Basically the requirements I needed to fulfill in the solution I’m working on are the following:
- The Web API should stay stateless, no cookies should be transmitted between the consumer and the API.
- Each call to the Web API should be authenticated using OAuth 2.0 Bearer tokens as we implemented before.
- Two Factor Authentication is required only for some sensitive API requests, in other words for some selective sensitive endpoints (i.e. transfer money) should ask for Two Factor Authentication time sensitive passcode along with the valid bearer access token, and the other endpoints will use only One Factor for authentication which is the OAuth 2.0 bearer access tokens only.
- We need to build cost effective solution, no need to add new cost by sending SMSs which contain a passcode, we will depend on our users’ smartphones as a Soft Token. The user needs to install Google Authenticator application on his/her smartphone which will generate a time sensitive passcode valid for only 30 seconds.
- Lastly the front-end application which will consume this API will be built using AngularJS and should support displaying QR codes for the Preshared key.
Before jumping into the implementation I’d like to emphasize some concepts to make sure that we understand the core components of Two Factor Authentication and how Google Authenticator works as Soft Token.
What is Two Factor Authentication?
Any user trying to access a system can be authenticated by one of the below ways:
- Something the user knows (Password, Secret).
- Something the user owns (Mobile Phone, Device).
- Something the user is (Bio-metric, fingerprint).
Two Factor authentication is a combination of any two of the above three ways. When want to apply this to real business world applications, we usually use the first and the second ways. This is because the third way (Biometrics) is very expensive and complicated to roll out, and the end user experience can be problematic.
So Two Factor authentication is made up of something that the user knows and another thing the user owns. The smartphone is something the user owns so receiving a passcode (receiving SMS or call) on it, or generating a passcode using the smartphone (as in our case) will allow us to add Two Factor authentication.
In our Back-end API we’ll ask the user to provide the below two separate factors when he/she wants to access a sensitive endpoint, the factors the user should provide are:
- The OAuth 2.0 bearer access tokens which is granted to the user when he provides his/her username and password at an earlier stage.
- The time sensitive passcode generated on the user smartphone which he/she owns using Google Authenticator.
What is Google Authenticator?
Google Authenticator is a mobile based application which is available on different platforms, the application works as a soft token which is responsible for generating time sensitive passcodes which will be used to implement Two Factor authentication for Google services. Basically the time sensitive passcode generated contains six digits which is valid for 30 seconds only, then new six digits will be generated directly.
Once you install the Google Authenticator application on your smartphone, it will basically ask you to enter Preshared Secret/Key between you and the application, this Preshared Key will be generated from our back-end API for each user register in our system and will be displayed on the UI as QR code or as plain text so the user can add this Preshared Key to Google Authenticator. More about generating this Preshared Key later in this post.
The nice thing about Google Authenticator is that it generates the time sensitive passcode on the smartphone without depending on anything, there is no need for internet connection or access to Google services to generate this passcode. How is this happening? Well the Google Authenticator implements Time-based One-time Password Algorithm (TOTP) which is an algorithm that computes a one-time password from a shared secret key and the current time, TOTP is based on HMAC-based One Time Password algorithm (HOTP) with a time-stamp replacing the incrementing counter in HOTP. You can check the implementation for Google Authenticator here.
In our solution the first step to authenticate the user is by providing his username and password in order to obtain an OAuth 2.0 bearer access token which is considered a type of knowledge-factor authentication, then as we agreed before and for certain selective sensitive API end-points (require second factor authentication), the user will open Google Authenticator application installed on his/her smartphone, lookup the time sensitive passcode which is generated from the Preshared key, and this passcode represents something the user owns (represents ownership-factor authentication).
Process flow for using Google Authenticaor with our Back-end API
In this section I’ll show you the process flow which the user will follow in our Two Factor authentication enabled solution in order to allow the user to access the selective sensitive API end-points.
- The user will register in our-back end system by providing username,password and confirm password (normal registration process), upon successful registration and in the back-end API, a Preshared Key (PSK) is generated for this user and returned to the UI in form of QR code and plain text (5FDAEHNBNM6W3L2S) so the user will open Google Authenticator application installed on his smartphone and scan this Preshared Key or enter it manually and select Time Based key. UI will look as the image below:
- Now the user will login to our system by providing his username/password and obtaining an OAuth 2.0 access token which will allow him to access our protected API resources, as long as he is trying to access non elevated sensitive end-point (i.e. transfer money) then our back-end API will be happy with only the one-factor authentication (knowledge-based factor) which is the OAuth 2.0 bearer access token only. As on the images blow the user is allowed to access his transactions history (non sensitive end point, yet it is protected by one-factor authentication).
- Now the user wants to perform an elevated sensitive request (Transfer Money), this end-point in out back-end API is configured to ask for second ownership factor to authenticate before completing the request, so the UI will ask the user to enter the time sensitive pass-code provided by Google Authenticator, the UI for Transfer Money will look as below:
- The user will fill the form with the details he wants, if he tried to issue a transfer request without providing the second factor authentication (pass code) the request will be rejected and HTTP 401 unauthorized will be returned because this endpoint is sensitive and needs a second ownership factor to authenticate successfully. So the user will grab his smartphone, open Google Authenticator application and type the six digits pass code appears that on the application and hit transfer button. This six digits code will be sent in a custom HTTP header along with the OAuth 2.0 access token to this sensitive end-point.
- Now the API receives this pass code for example (472307), and checks the code validity, if it is valid then the API will process the request and the user will complete transferring the money successfully (execution for the sensitive end-point is successful).
The last step is the trickiest one, you are not asking yourself how this six digits code generated by Google Authenticator which will keep changing every 30 seconds will be understood and considered authentic by our back-end API. To answer this we need to take a brief look on how Google Authenticator produces those six digits; because we’ll simulate the same procedure at our back-end API.
How does Google Authenticator work?
As we have stated before Google Authenticator supports both TOTP algorithm on top of HOTP algorithm for generating onetime pass-codes or time sensitive pass-codes.
The idea behind HOTP is that the server (our back-end API) and client (Google Authenticator app) share a Preshared Key value and a counter, The preshared key and the counter are used to compute a one-time passcode independently on both sides (Notice it is a one-time passcode not a time sensitive passcode). Whenever a passcode is generated and used, the counter is incremented on both sides, allowing the server and client to remain in sync.
TOTP is built on top of HOTP, it uses HOTP same algorithm with one clear difference; the counter used in TOTP is replaced by the current time. But the challenge here is that this six digits passcode will keep changing rapidly with every second change and this will not be feasible because the end user will not be able to enter this number when asked for.
To solve this issue we’ll generate the number (counter) corresponding to 00 second of the current minute and let it remain constant for the next 30 seconds; by doing this the six digits passcode will become usable and will stay constant for the next 30 seconds. Note that all time calculations are based on UTC, so there is no need to worry about the time zone for the server and the time zone for the user using Google Authenticator application.
When the sensitive API endpoint receives this time sensitive passcode, it will repeat the process described above. Basically the API will know the user id from the OAuth access token sent, then fetch the Preshared key saved in the database for this user, and from there it will generate the time sensitive passcode and compare it with the passcode sent from the client application (UI), if the passcode sent form the UI matches the one generated in the back-end API then the API will consider this passcode authentic and process the request successfully.
Note: One of the greatest books which describes how Google Authenticator works is Pro ASP.NET Web API Security (Chapter 14) written by Badrinarayanan Lakshmiraghavan. Highly recommend if you are interested in ASP.NET Web API security in general.
Now it is time to start implementing this solution, this is the longest theory I’ve written so far in all my blog posts so we’d better to move to the code 🙂
Building the Back-End API
I highly recommend to read my previous post Token Based Authentication before implementing the steps below and enabling Two Factor Authentication, because the steps mentioned below are very identical to the previous post, so I’ll list the identical steps and will be very brief in explaining what each step is doing except for the new steps which are related to enabling Two Factor Authentication in our back-end API.
Step 1: Creating the Web API Project
Create an empty solution and name it “TFAWebApiAngularJS” then add a new ASP.NET Web application named “TwoFactorAuthentication.API”, the selected template for the project will be “Empty” template with no core dependencies. Notice that the authentication is set to “No Authentication” taking into consideration that we’ll add this manually.
Step 2: Installing the needed NuGet Packages:
1 2 3 4 5 6 7 |
Install-Package Microsoft.AspNet.WebApi -Version 5.2.2 Install-Package Microsoft.AspNet.WebApi.Owin -Version 5.2.2 Install-Package Microsoft.Owin.Host.SystemWeb -Version 3.0.0 Install-Package Microsoft.Owin.Cors -Version 3.0.0 Install-Package Microsoft.Owin.Security.OAuth -Version 3.0.0 Install-Package Microsoft.AspNet.Identity.Owin -Version 2.0.1 Install-Package Microsoft.AspNet.Identity.EntityFramework -Version 2.0.1 |
Step 3: Add Owin “Startup” Class
Right click on your project then add a new class named “Startup”. It will contain the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public class Startup { public void Configuration(IAppBuilder app) { HttpConfiguration config = new HttpConfiguration(); ConfigureOAuth(app); WebApiConfig.Register(config); app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); app.UseWebApi(config); } public void ConfigureOAuth(IAppBuilder app) { OAuthBearerAuthenticationOptions OAuthBearerOptions = new OAuthBearerAuthenticationOptions(); OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions() { //For Dev enviroment only (on production should be AllowInsecureHttp = false) AllowInsecureHttp = true, TokenEndpointPath = new PathString("/token"), AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), Provider = new SimpleAuthorizationServerProvider() }; // Token Generation app.UseOAuthAuthorizationServer(OAuthServerOptions); //Token Consumption app.UseOAuthBearerAuthentication(OAuthBearerOptions); } } |
Basically this class will configure our back-end API to use OAuth 2.0 bearer tokens to secure the endpoints attribute with [Authorize] attribute, as well it also set the access token to expire after 24 hours. We’ll implement the class “SimpleAuthorizationServerProvider” in later steps.
Step 4: Add “WebApiConfig” class
Right click on your project, add a new folder named “App_Start”, inside this class add a class named “WebApiConfig”, then paste the code below:
1 2 3 4 5 6 7 8 9 10 11 |
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API routes config.MapHttpAttributeRoutes(); var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First(); jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); } } |
Step 5: Add the ASP.NET Identity System
Now we’ll configure our user store to use ASP.NET Identity system to store the user profiles, to do this we need a database context class which will be responsible for communicating with our database, so add a new class and name it “AuthContext” then paste the code snippet below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class AuthContext : IdentityDbContext<ApplicationUser> { public AuthContext() : base("AuthContext") { } } public class ApplicationUser : IdentityUser { [Required] [MaxLength(16)] public string PSK { get; set; } } |
As you see in the code above, the “AuthContext” class is inheriting from “IdentityDbContext<ApplicationUser>”, where the “ApplicationUser” inherits from “IdentityUser”, this is done like this because we need to extend the “AspNetUsers” table and add a new column named “PSK” which will contain the Preshared key generated for this user in our back-end API. More on generating this Preshared key later in the post.
Now we want to add “UserModel” which contains the properties needed to be sent once we register a user, this model is a POCO class with some data annotations attributes used for the sake of validating the registration payload request. So add a new folder named “Models” then add a new class named “UserModel” and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class UserModel { [Required] [Display(Name = "User name")] public string UserName { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } } |
Now we need to add a new connection string named “AuthContext” in our Web.Config file, so open you web.config and add the below section:
1 2 3 |
<connectionStrings> <add name="AuthContext" connectionString="Data Source=.\sqlexpress;Initial Catalog=TFAAuth;Integrated Security=SSPI;" providerName="System.Data.SqlClient" /> </connectionStrings> |
Step 6: Add Repository class to support ASP.NET Identity System
Now we want to implement two methods needed in our application which they are: “RegisterUser” and “FindUser”, so adda new class named “AuthRepository” and paste the code snippet below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public class AuthRepository :IDisposable { private AuthContext _ctx; private UserManager<ApplicationUser> _userManager; public AuthRepository() { _ctx = new AuthContext(); _userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(_ctx)); } public async Task<IdentityResult> RegisterUser(UserModel userModel) { ApplicationUser user = new ApplicationUser { UserName = userModel.UserName, TwoFactorEnabled = true, PSK = OneTimePass.GenerateSharedPrivateKey() }; var result = await _userManager.CreateAsync(user, userModel.Password); return result; } public async Task<ApplicationUser> FindUser(string userName, string password) { ApplicationUser user = await _userManager.FindAsync(userName, password); return user; } public void Dispose() { _ctx.Dispose(); _userManager.Dispose(); } } |
By looking at the code above you will notice that we are generating Preshared key for the registered user by calling the static method “OneTimePass.GeneratePresharedKey”, this Preshared key will be sent back to the end user so he will enter this 16 characters key in his Google Authenticator application.
Step 7: Add support for generating Preshared keys and passcodes
Note: There are lot of implementations for Google Authenticator algorithms (HOTP, and TOTP) out there in different platforms including .NET, but there is nothing that beats Badrinarayanan Lakshmiraghavan’s implementation in simplicity and minimal number of code used. The implementation exists and is available for public in the companion source code which comes with his Pro ASP.NET Web API Security book, so all the credit for the below implementation goes for Badrinarayanan, and I’ll not re-explain how the implementation is done. Badrinarayanan explained it in a very simple way, so my recommendation is to check his book.
So add a new folder named “Services” and inside it add a new file named “TimeSensitivePassCode.cs” then paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Web; using System.Text; namespace TwoFactorAuthentication.API.Services { public static class TimeSensitivePassCode { public static string GeneratePresharedKey() { byte[] key = new byte[10]; // 80 bits using (var rngProvider = new RNGCryptoServiceProvider()) { rngProvider.GetBytes(key); } return key.ToBase32String(); } public static IList<string> GetListOfOTPs(string base32EncodedSecret) { DateTime epochStart = new DateTime(1970, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc); long counter = (long)Math.Floor((DateTime.UtcNow - epochStart).TotalSeconds / 30); var otps = new List<string>(); otps.Add(GetHotp(base32EncodedSecret, counter - 1)); // previous OTP otps.Add(GetHotp(base32EncodedSecret, counter)); // current OTP otps.Add(GetHotp(base32EncodedSecret, counter + 1)); // next OTP return otps; } private static string GetHotp(string base32EncodedSecret, long counter) { byte[] message = BitConverter.GetBytes(counter).Reverse().ToArray(); //Intel machine (little endian) byte[] secret = base32EncodedSecret.ToByteArray(); HMACSHA1 hmac = new HMACSHA1(secret, true); byte[] hash = hmac.ComputeHash(message); int offset = hash[hash.Length - 1] & 0xf; int truncatedHash = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff); int hotp = truncatedHash % 1000000; return hotp.ToString().PadLeft(6, '0'); } } public static class StringHelper { private static string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; public static string ToBase32String(this byte[] secret) { var bits = secret.Select(b => Convert.ToString(b, 2).PadLeft(8, '0')).Aggregate((a, b) => a + b); return Enumerable.Range(0, bits.Length / 5).Select(i => alphabet.Substring(Convert.ToInt32(bits.Substring(i * 5, 5), 2), 1)).Aggregate((a, b) => a + b); } public static byte[] ToByteArray(this string secret) { var bits = secret.ToUpper().ToCharArray().Select(c => Convert.ToString(alphabet.IndexOf(c), 2).PadLeft(5, '0')).Aggregate((a, b) => a + b); return Enumerable.Range(0, bits.Length / 8).Select(i => Convert.ToByte(bits.Substring(i * 8, 8), 2)).ToArray(); } } } |
Briefly what we’ve implemented in this class is the below
- We’ve added a static method named “GeneratePresharedKey” which is responsible for generating 16 characters as our Preshared key, basically it is an array of 80 bit which is encoded using base32 format, this base32 format uses 26 letters A-Z and six digits 2-7 which will produce restricted set of characters that can be conveniently used by end users.
- Why did we encod the key using base32 format? Because Google Authenticator uses the same encoding to help end users who prefer to enter the Preshared key manually without any confusion, the numbers which might confuse the users with letters are omitted (i.e 0,1,8,9). The implementation for Base32 encoding can be found on the extension method named “ToBase32String” in the helper class “StringHelper”.
- We’ve implemented the static method “GetHotp” which accepts the base32 encoded Preshared key (16 characters) and a counter, this method will be responsible for generating the One time passcodes.
- As we stated before the implementation of TOTP is built on top of HOTP, the only major difference is that the counter is replaced by time, so in the method “GetListOfOTPs” we are getting three time sensitive pass codes, one in the past, another in the present, and one in the future; the reason for doing this is to accommodate for the clock alter/shift between the server time and the clock on the smartphone where the passcode is generated using Google Authenticator, we are basically making it easy for the end user when he enters the time sensitive passcodes.
Step 8: Add our “Account” Controller
Now we’ll add a Web API controller which will be used to register a new users, so under add new folder named “Controllers” then add an Empty Web API 2 Controller named “AccountController” and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
[RoutePrefix("api/Account")] public class AccountController : ApiController { private AuthRepository _repo = null; public AccountController() { _repo = new AuthRepository(); } // POST api/Account/Register [AllowAnonymous] [Route("Register")] public async Task<IHttpActionResult> Register(UserModel userModel) { if (!ModelState.IsValid) { return BadRequest(ModelState); } IdentityResult result = await _repo.RegisterUser(userModel); IHttpActionResult errorResult = GetErrorResult(result); if (errorResult != null) { return errorResult; } ApplicationUser user = await _repo.FindUser(userModel.UserName, userModel.Password); return Ok(new { PSK = user.PSK }); } protected override void Dispose(bool disposing) { if (disposing) { _repo.Dispose(); } base.Dispose(disposing); } private IHttpActionResult GetErrorResult(IdentityResult result) { if (result == null) { return InternalServerError(); } if (!result.Succeeded) { if (result.Errors != null) { foreach (string error in result.Errors) { ModelState.AddModelError("", error); } } if (ModelState.IsValid) { // No ModelState errors are available to send, so just return an empty BadRequest. return BadRequest(); } return BadRequest(ModelState); } return null; } } |
It is worth noting here that inside method “Register” we’re returning the SPK after registering the user successfully, this PSK will be displayed on the UI on form of QR code and plain text so we give the user 2 options to enter it in Google Authenticator.
Step 9: Add Protected Money Transactions Controller with Two Actions
Now we’ll add a protected controller which can be accessed only if the request contains valid OAuth 2.0 bearer access token, inside this controller we’ll add 2 action methods, one of those action methods “GetHistory” will only requires One-Factor authentication (only bearer tokens) to process the request, on the other hand there will be another sensitive action method named “PostTransfer” which will require Two Factor authentication to process the request.
So add new Web API controller named “TransactionsController” under folder Controllers and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
[Authorize] [RoutePrefix("api/Transactions")] public class TransactionsController : ApiController { [Route("history")] public IHttpActionResult GetHistory() { return Ok(Transaction.CreateTransactions()); } [Route("transfer")] [TwoFactorAuthorize] public IHttpActionResult PostTransfer(TransferModeyModel transferModeyModel) { return Ok(); } } #region Helpers public class Transaction { public int ID { get; set; } public string CustomerName { get; set; } public string Amount { get; set; } public DateTime ActionDate { get; set; } public static List<Transaction> CreateTransactions() { List<Transaction> TransactionList = new List<Transaction> { new Transaction {ID = 10248, CustomerName = "Taiseer Joudeh", Amount = "$1,545.00", ActionDate = DateTime.UtcNow.AddDays(-5) }, new Transaction {ID = 10249, CustomerName = "Ahmad Hasan", Amount = "$2,200.00", ActionDate = DateTime.UtcNow.AddDays(-6)}, new Transaction {ID = 10250,CustomerName = "Tamer Yaser", Amount = "$300.00", ActionDate = DateTime.UtcNow.AddDays(-7) }, new Transaction {ID = 10251,CustomerName = "Lina Majed", Amount = "$3,100.00", ActionDate = DateTime.UtcNow.AddDays(-8)}, new Transaction {ID = 10252,CustomerName = "Yasmeen Rami", Amount = "$1,100.00", ActionDate = DateTime.UtcNow.AddDays(-9)} }; return TransactionList; } } public class TransferModeyModel { public string FromEmail { get; set; } public string ToEmail { get; set; } public double Amount { get; set; } } #endregion } |
Notice that the “Authorize” attribute is set on the controller level for both actions, which means that both actions need the bearer token to process the request, but on the action method “PostTransfer” you will notice that there is new custom authorize filter attribute named “TwoFactorAuthorizeAttribute” setting on top of this action method, this custom authorize attribute is responsible for enabling Two Factor authentication on any sensitive controller, action method, or HTTP verb in future. All we need to do is just use this custom attribute as an attribute on the endpoint we want to elevate the security level on and require a second factor authentication to process the request.
Step 10: Implement the “TwoFactorAuthorizeAttribute”
Before starting to implement the “TwoFactorAuthorizeAttribute” we need to add simple helper class which will be responsible to inspect request headers looking for the time sensitive passcode sent by the client application in a custom header, so to implement this add a new folder named “Helpers” and inside it add a new file named “OtpHelper” and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public static class OtpHelper { private const string OTP_HEADER = "X-OTP"; public static bool HasValidTotp(this HttpRequestMessage request, string key) { if (request.Headers.Contains(OTP_HEADER)) { string otp = request.Headers.GetValues(OTP_HEADER).First(); // We need to check the passcode against the past, current, and future passcodes if (!string.IsNullOrWhiteSpace(otp)) { if (TimeSensitivePassCode.GetListOfOTPs(key).Any(t => t.Equals(otp))) { return true; } } } return false; } } |
So basically this class is looking for a custom header named “X-OTP” in the HTTP request headers, if this header was found we’ll send the value of it (time sensitive passcode) along with Preshared key for this authenticated user to the method “GetListOfOTPs” which we defined in step 7.
If this time sensitive passcode exists in the list of pass codes (past, current, and future passcodes) then this means that this passcode is valid and authentic, otherwise the passcode is invalid or the user didn’t include it in the request.
Now to implement the custom “TwoFactorAuthorizeAttribute” we need to add a new folder named “Filters” and inside it add a new class named “TwoFactorAuthorizeAttribute” then paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class TwoFactorAuthorizeAttribute : AuthorizationFilterAttribute { public override Task OnAuthorizationAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken) { var principal = actionContext.RequestContext.Principal as ClaimsPrincipal; var preSharedKey = principal.FindFirst("PSK").Value; bool hasValidTotp = OtpHelper.HasValidTotp(actionContext.Request, preSharedKey); if (hasValidTotp) { return Task.FromResult<object>(null); } else { actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new CustomError() { Code = 100, Message = "One Time Password is Invalid" }); return Task.FromResult<object>(null); } } } public class CustomError { public int Code { get; set; } public string Message { get; set; } } |
What we’ve implemented in this customer authorization filer is the following:
- This custom Authorize attribute inherits from “AuthorizationFilterAttribute” and we are overriding the “OnAuthorizationAsync” method.
- We can be 100% sure that the code execution flow will not reach this authorization filter attribute if the user is not authenticated by the OAuth 2.0 bearer token sent, this custom authorize attribute run later in the pipeline after the “Authorize” attribute.
- Inside the “OnAuthorizationAsync” method we are looking for the claims for the authenticated user, this claim will contain custom claim of type “PSK” which contains the value of the Preshared key for this authenticated user (Didn’t implement this yet, you will see how we set the claim in the next step).
- Then we will call the helper method named “OtpHelper.HasValidTotp” by passing the HTTP request which contains the time sensitive pass code in a custom header along with the Preshared key. If this method returns true then we will consider this request a valid one and that has fulfilled the Two Factor authentication requirements.
- If the request doesn’t contain the valid time sensitive passcode then we will return HTTP status code 401 along with a message and an arbitrary integer code used in the UI.
Step 11: Implement the “SimpleAuthorizationServerProvider” class
Add a new folder named “Providers” then add new class named “SimpleAuthorizationServerProvider”, paste the code snippet below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider { public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { context.Validated(); return Task.FromResult<object>(null); } public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { var allowedOrigin = "*"; ApplicationUser appUser = null; context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin }); using (AuthRepository _repo = new AuthRepository()) { appUser = await _repo.FindUser(context.UserName, context.Password); if (appUser == null) { context.SetError("invalid_grant", "The user name or password is incorrect."); return; } } var identity = new ClaimsIdentity(context.Options.AuthenticationType); identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName)); identity.AddClaim(new Claim(ClaimTypes.Role, "User")); identity.AddClaim(new Claim("PSK", appUser.PSK)); var props = new AuthenticationProperties(new Dictionary<string, string> { { "userName", context.UserName } }); var ticket = new AuthenticationTicket(identity, props); context.Validated(ticket); } public override Task TokenEndpoint(OAuthTokenEndpointContext context) { foreach (KeyValuePair<string, string> property in context.Properties.Dictionary) { context.AdditionalResponseParameters.Add(property.Key, property.Value); } return Task.FromResult<object>(null); } } |
It is worth mentioning here that in the second method “GrantResourceOwnerCredentials” which is responsible for validating the username and password sent to the authorization server token endpoint, and after fetching the user from the database, we are adding a custom claim named “PSK”, the value for this claim contains the Preshared key for this authenticated user. This claim will be included in the signed OAuth 2.0 bearer token, that’s why we can directly get the PSK value in step 10.
Step 12: Testing the Back-end API
First step to test the API is to register a new user so open your favorite REST client application in order to issue an HTTP request to register the user, so issue an HTTP POST request to the endpoint https://ngtfaapi.azurewebsites.net/api/account/register as the image below:
If the request processed successfully you will receive 200 status along with the Preshared Key, so open Google Authenticator app and enter this key manually.
Now we need to obtain OAuth 2.0 bearer access token to allow us to request the protected end points, this represent the first factor authentication because the user will provide his username and password, so we need to issue HTTP POST request to the endpoint http://ngtfaapi.azurewebsites.net/token as the image below:
Now after we have a valid OAuth 2.0 access token, we need to try to send an HTTP POST request to the protected elevated security endpoint which requires second factor authentication, we’ll issue the request to the endpoint https://ngtfaapi.azurewebsites.net/api/transactions/transfer asshown in the image below, but we’ll not set the value for the custom header (X-OTP) so definitely the API will response with 401 unauthorized access
Now in order to make this request authentic we need to open Google Authenticator and get the passcode from there and send it in the (X-OTP) custom header, so the authentic request will be as shown in the image below:
The live AngularJS demo application is hosted on Azure, the source code for this tutorial on GitHub.
That’s it for now folks!
I hope this step by step post will help you enable Two Factor Authentication into ASP.NET Web API RESTful services for selective sensitive endpoints, if you have any question please drop me a comment.
Follow me on Twitter @tjoudeh
References
- The great Pro ASP.NET Web API Security book by Badrinarayanan Lakshmiraghavan, highly recommended for anyone interested in ASP.NET Web API security.
- Using Google Authenticator For Your Website.
- TOTP and HTOP algorithm.
- Google Authenticator Project.
- Reviewed by Diana Nasser.
very useful tutorial !
I’m wondering if it’s possible to use the same approach but with out-of-the-box two factor authentication shipped with Identity Framework 2.0 (either with email, sms, or whatsoever implementing IIdentityMessageService interface)
Hi Amr,
The built in TFA in asp.net identity depends on cookies to set the Auth code, as you noticed I try not to use cookies when dealing with RESTful API, but you can send the code using the SMS when you want to access elevated security API end point using the SMS, this code is not time sensitive unless you do the DB checks when you receive it to make sure it is not too old (valid for 30 minutes or until the user use it at least one time).
Great article, with superb explanations and very easy to follow. Thank you
You are welcome Mark, glad it was nice read 🙂
Excellent ! Thank you very much for sharing!
You welcome Vasilis, glad it was useful 🙂
Great tutorial, the best that I did find, helped me a lot.
great tut ……
Thanks for the great article! By the way, you have some mistake in your client code on github. In authInterceptorService.js in code lines:
var _responseError = function (rejection) {
if (rejection.status === 401) {
if ((rejection.data.code) && (rejection.data.code = 100))
{
//Case that OTP is not valid but access token is valid
return $q.reject(rejection);
}
var authService = $injector.get(‘authService’);
authService.logOut();
$location.path(‘/login’);
}
return $q.reject(rejection);
}
It must be rejection.data.code === 100 I think. 😉
Thanks for the heads up, you are correct, will update the repo soon.
Can you suggest the best place and method in application to catch broot force atack. Because it’s only 6 digits and 1 minute is enough time to find the right code. I think it must be max attempts, but where to implement it?
I mean brute force, sorry for my English. I think that it could be another one blog post about rate-limiting. Because it is very important question for Web Services ( Brute Force and DOS attacks).
Hi,
This is unlikely to happen, this is the second factor to proof tour owner ship of the resource, so to start DOS attack you need a valid token before (You know resource owner password). Usually you use 2FA to protect your service from DOS attacks.
Yes, you’re right. If… If I know password, then 2FA must help. But brute forcing makes it easy to kill the second line of security. Yes, it is unlikely to happen, but it could happen. If I know password of this man, then I have 1 minute to hack the second code, So I must post about 900000 requests ( i think it will be much less then that number) per minute. So in this case I will do one of two things: or brute guess the code, or DOS the server. But, i think that this is what the server must protect us from. Nginx and IIS both have functions to limit requests from one ip.
Why I’m asking? Because I saw rate-limiting at github api server docs: https://developer.github.com/v3/rate_limit/
If it is so unlikely to happen, why they develop such things?
You might be interested to check Web API throttling library which helps to achieve what are you trying to do: https://throttlewebapi.codeplex.com/
Thank you very much! I didn’t see this library. I’m only learning, and your blog helps me. Thanks for your work, good luck!
You are most welcome, glad it was useful.
Idon’t know is this is a good practise but I found that these codelines could help in my question:
public class TwoFactorAuthorizeAttribute : AuthorizationFilterAttribute
{
public override Task OnAuthorizationAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
var key = string.Concat(“2FA”, “-“, HttpContext.Current.Request.UserHostAddress);
if (HttpContext.Current.Cache[key] == null)
{
HttpRuntime.Cache.Add(key,
true, // is this the smallest data we can have?
null, // no dependencies
DateTime.Now.AddSeconds(20), // absolute expiration
Cache.NoSlidingExpiration,
CacheItemPriority.Low,
null); // no callback
}
else
{
actionContext.Response = actionContext.Request
.CreateResponse(
HttpStatusCode.Unauthorized,
new CustomError
{
Code = 100,
Message = “Please wait for 20 seconds”
});
return Task.FromResult(null);
}
//…. other codelines
It was really easy to understand the way you explained
Thanks, Glad you liked it 🙂
Great and nice article.
I have used your code to try an implementation of 2FA in my project. But I have something strange.
After calling GeneratePresharedKey() the code generate a nice base32 string. This string has valide for the Google authenticator. But after calling GetListOfOTPs(“MYBASE32CODE”) it return’s 3 6 digit’s codes back, but non of the are match the Google authenticator. I have check the server time and timezone. Also I have try to change the DateTimeKind.Utc to DateTimeKind.local and DateTime.UtcNow to DateTime.now.
Do you have any idea what is going wrong?
Thanks for the help!
Joël
Hi Joel,
That is really strange, but let’s do the following to help us isolation the cause for your issue:
Now on the live demo application you please register an account and then add the base32 code returned to your Google authenticator application on your mobile.
The you need to obtain a bearer access token by issuing POST request to the end point: http://ngtfaapi.azurewebsites.net/token and setting the content-type header to “application/x-www-form-urlencoded” and send the data as the below request:
grant_type=password&username=hamzajoudeh&password=password
Lastly you need to issue a POST request using your REST client to the end point: http://ngtfaapi.azurewebsites.net/api/transactions/transfer containing the below request body:
{
“amount”: 12345
“fromEmail”: “hamza@mail.com”
“oneTimePassword”: YOUR OTP GOES HERE
“toEmail”: “mymail@mail.com”
}
Do not forget to set the AuthZ header bearer token using the access token obtained in the previous request.
Now if this works and you get 200 OK then the time in your mobile application and Azure server is correct and the issue is in your local dev server timing, if this didn’t work then the issue with the time in your mobile application. We should not care about the time zones because we are using Unix timing. Make sure that there is no more than +/- 1 minute difference between your mobile application and your local server timing.
Hope this helps to trouble shoot your issue, the Github code is working correctly and the demo application using the same code base in the repo.
Hi Taiseer,
Thanks for your help! I have running the steps and all seems to work correct. So the problem is in my project. I have cleared al the classes from the server en build to code again. It seems there was some conflict on the function ToBase32String. There was on old class active where the same function name exists…
So I have this working now! Thanks for sharing this with us 🙂
Kind regards,
Joël
Glad to help Joel 🙂
Great tutorial! It helped me a lot… I have one, actually two question. In token response, is there any way to change names in returned JSON? For example, can it be auth_token instead of access_token?
I’m gonna be using two types of tokens and it’s easier and more clear if they have different names. I was trying to find out how to change this, but no success till now. My second question is also pointed to token response. Can I change .issued and .expires date formats somewhere? For example instead of long date can I return date in yyyy-mm-dd format?
I’ve did a little research and found out that those fields are parts of OAuth2 standard and that it’s not recommended to change them. But I have new question now… Is it ok if I pass issued and .expires values as long instead as string to a client?
You can add extra param response by overriding the “TokenEndpoint” in class “SimpleAuthorizationServerProvider”, but as you suggested it is not recommended to change the property name of “access_token” to something else.
And if I for example need to create one more token that is made out of mentioned access_token and otp_code, can I do it by overriding some other method?
Thank you for the article!
But how to do it if you want the user to enter the code at login? Should I first check the username/password in a controller and then add the X-OTP header when calling api/token? Or…?
Hi Ivar,
Yest the TOP should be sent with a valid access token, then you validate the OTP sent the X-OTP header
Hi,
There is two-factor authentication option in Identity framework, but they all depends on cookie to complete its validation cycle. If we need to use identity framework supported two-factor authentication (sms/ email) in AngularJs + WebAPI applicaiton , how can we implement it. Have you tried it ?
Thank you for the article
can you help me to resolve this error message :
“The name ‘ OneTimePass ‘ does not exist in the current context” in class AuthRepository
“OneTimePass.GenerateSharedPrivateKey”
thank
Hi:
I am using vs 2015 to load your project and after I registered a user, I cannot find the record on a local sql database where I created those aspnetuser table manually. Can you show me how to get the register user store in the sql database. I cannot find a way to generate the database from your project.
Hi Johnson,
I’m not sure where you issue resides, but I believe there is an exception happening when you create a user that’s why you are not seeing it in the AspNetUsers table.
But the data is stored ok in angularjs, so is your application suppose to save to sql database also or i have to wire it up with entity framework?
Great article!
I want to use this as a optional step during login. After successful login a token is received. A user who has enabled two factor authentication should be redirected to a new page where the OTP must be provided.
After the first step, where the user gets a token, how can I respond to the client that the user needs to go through the OTP step? I guess I need to check it for every request? How can I do this?
I’m thinking of using a custom grant type (grant_type=totp) to solve the OTP step. Here a new token will be created with an extra claim to remember that the user is two factor authenticated.
Hi Peter,
Yes exactly the same implementation i am looking for to implement in my project at the time of login user should be redirected to OTP page if the user has enable the 2FA with his profile.Please help me out on such situation how we can proceed with this approach.
Hi Taiseer,
Your articles are brilliant! Thank you.
I am trying to implement a Web Api with 2 factor authentication enabled.
I want the user to specify username, password as well as the OTP, while logging in itself.
How do I go about it?
Your articles on OAuth helped me immensly in the past and this excellent article has done so too.
Thank you very much.
You are most welcome Colin, happy to hear this 🙂
Hi Taiseer Joudeh,
It is very helpful article. I implemented the same but I facing an issue i.e.
When I deployed the application code on UTC+8:00 and my Google Authenticator app is in UTC+5:30, whenever I am entering the “Pass Code” then getting Invalid Pass Code.
Is it necessary to have server and authenticator app in same Timezone? If not How I can resolve this issue?
Thank you for this great article.
I am new at this so forgive me if my question doesn’t make sense.
How can I implement this if I already have my own SQL DB with an existing User table that contains username and password? Is it mandatory to use the default ASP.Net table. If not, can you please point me to how this can be done.?
Hi Patel,
Nop you can use whatever DB type or Repo scheme to implement this.
can you help me to resolve this
Where is the this static method? please
OneTimePass.GenerateSharedPrivateKey
thank you
Thank you very much for your such a nice and helpful article.
I have followed your below mentioned article to implement token based authentication in webapi,
https://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
I want to add 2 factor authentication using SMS in that same solution and not 2FA using google authenticator.
I have tried impelementing same but no luck and always I am getting SignInStatus = Success though I have two factor enabled for the user.
Can you help?
Issue resolved! I missed to register two factor authentication providers in application user manager. Thanks!
Great post! Is there a way to implement it in the authentication, I have not found the way to do it. Thanks
Very useful article. Just one question –
Will this TOTP generation logic work good for other authenticator apps too (say for example, Microsoft Authenticator)?
Will this TOTP generation logic work good for other authenticator apps too (say for example, Microsoft Authenticator)?