Be Sociable, Share!

Tweet



Email

WhatsApp



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.

, in other words for some selective sensitive endpoints (i.e. transfer money) should ask for Two Factor Authentication 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.

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:

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 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:

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); } } 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:

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(); } } 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:

public class AuthContext : IdentityDbContext<ApplicationUser> { public AuthContext() : base("AuthContext") { } } public class ApplicationUser : IdentityUser { [Required] [MaxLength(16)] public string PSK { get; set; } } 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:

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; } } 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:

<connectionStrings> <add name="AuthContext" connectionString="Data Source=.\sqlexpress;Initial Catalog=TFAAuth;Integrated Security=SSPI;" providerName="System.Data.SqlClient" /> </connectionStrings> 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:

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(); } } 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:

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(); } } } 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:

[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; } } 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:

[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 } 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:

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; } } 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:

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; } } 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:

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); } } 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