In this article I will explain with an example, how to implement 2 Factor Authentication (TOTP) using Microsoft Authenticator App in ASP.Net using C# and VB.Net.
This article makes use of OtpSharp library for implementing TOTP.
What is TOTP?
TOTP stands for Time-based one-time password.
It is basically a Two Factor authentication where a temporary OTP (One-Time-Password) is generated.
TOTP is also known as app based authentication, software tokens or soft tokens.
The OTP is generated using current Server time for uniqueness.
TOTP uses Shared Key Authentication (SKA) by which it gains the access of wireless network or device.
Advantages of TOTP
1. It is lightweight.
2. It provides quicker authentication.
3. It is easy to implement as compared to other authentications like hardware tokens, Biometric authentication and etc.
4. TOTP makes your online account more secured by adding extra layers of security.
5. It provides offline supports as the Microsoft Authenticator can generate OTP without the internet connection.
Installing OtpSharp.Core package using Nuget
Installing QRCoder package using Nuget
Installing Microsoft Authenticator App
In order to install Microsoft Authenticator app, first search for Microsoft Authenticator in Google Play store or App store and click on Install.
Or you can download and install the Microsoft Authenticator app using the following link.
HTML
The HTML markup consists of following controls:
Image – For displaying QR code.
HiddenField – For storing shared secret key.
TextBox – For capturing OTP.
Button – For validating OTP entered in the TextBox.
The Button has been assigned with an OnClick event handler.
Label – For displaying appropriate message based on validation of OTP.
<asp:Image ID="imgQRCode" runat="server" Width="200" Height="200" />
<asp:HiddenField ID="hfSecretKey" runat="server" />
<br />
<asp:TextBox ID="txtOTP" runat="server" placeholder="Enter OTP"></asp:TextBox>
<asp:Button ID="btnValidate" runat="server" Text="Validate OTP" OnClick="OnValidate" />
<br /><br />
<asp:Label ID="lblMessage" runat="server"></asp:Label>
Namespaces
You will need to import the following namespaces.
C#
using System.IO;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Security.Cryptography;
using OtpSharp;
using QRCoder;
VB.Net
Imports System.IO
Imports System.Text
Imports System.Drawing
Imports System.Drawing.Imaging;
Imports System.Security.Cryptography
Imports OtpSharp
Imports QRCoder
Generating OAUTH URI and QRCode for TOTP authentication
Inside the Page_Load event handler, issuer name and account name is set and issuer name is encoded using UrlEncode method of HttpUtility class which will be used later to construct the URI and random secret key is generated using the GenerateRandomKey method.
Generating Random Secret Key
Inside the GenerateRandomKey method, an object of RNGCryptoServiceProvider class is created and based on the length, the secretKey is generated and returned.
Then, the generated secret key is stored into a HiddenField and encoded into BYTE Array and then converted into BASE32 string.
Once the encoded secret key is ready, the URI is generated using the following parameters:
Issuer – Name of issuer.
Account – The email account.
Secret Key – BYTE array of secret key generated earlier in this code.
OtpHashMode – Used for calculating the code in OTP (One Time Password).
After that, the QR code is generated for the URI and displayed using Image control.
C#
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
string issuer = "ASPSnippets";
string account = "queries@aspsnippets.com";
string encodedIssuer = HttpUtility.UrlEncode(issuer);
// Generate random secret key.
string secretKey = this.GenerateRandomKey(20);
// Storing the secret key.
hfSecretKey.Value = secretKey;
string encodedSecretKey = this.ToBase32String(Encoding.UTF8.GetBytes(secretKey));
// Generate OAuthUri.
string otpAuthUri = string.Format("otpauth://totp/{0}:{1}?secret={2}&{3}&issuer={4}&algorithm={5}", issuer, account, encodedSecretKey.Trim('='), secretKey, encodedIssuer, OtpHashMode.Sha1);
// Generate QR code image.
using (QRCodeGenerator qrGenerator = new QRCodeGenerator())
{
using (QRCodeData qrCodeData = qrGenerator.CreateQrCode(otpAuthUri, QRCodeGenerator.ECCLevel.Q))
{
using (QRCode qrCode = new QRCode(qrCodeData))
{
using (Bitmap qrCodeImage = qrCode.GetGraphic(20))
{
// Convert QR code image to byte array.
using (MemoryStream stream = new MemoryStream())
{
qrCodeImage.Save(stream, ImageFormat.Png);
byte[] bytes = stream.ToArray();
string base64String = Convert.ToBase64String(bytes);
// Display QR code.
imgQRCode.ImageUrl = "data:image/png;base64," + base64String;
}
}
}
}
}
}
}
private string GenerateRandomKey(int length)
{
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
byte[] key = new byte [length];
rng.GetBytes(key);
return Convert.ToBase64String(key);
}
}
private static string ToBase32String(byte[] data)
{
// Base32 character set.
const string base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
StringBuilder result = new StringBuilder((data.Length * 8 + 4) / 5);
int buffer = data[0];
int next = 1;
int bitsLeft = 8;
while (bitsLeft > 0 || next < data.Length)
{
if (bitsLeft < 5)
{
if (next < data.Length)
{
buffer <<= 8;
buffer |= data[next++] & 0xFF;
bitsLeft += 8;
}
else
{
int pad = 5 - bitsLeft;
buffer <<= pad;
bitsLeft += pad;
}
}
int index = 0x1F & (buffer >> (bitsLeft - 5));
bitsLeft -= 5;
result.Append(base32Chars[index]);
}
return result.ToString();
}
VB.Net
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
If Not Me.IsPostBack Then
Dim issuer = "ASPSnippets"
Dim account = "queries@aspsnippets.com"
Dim encodedIssuer As String = HttpUtility.UrlEncode(issuer)
' Generate random secret key.
Dim secretKey As String = Me.GenerateRandomKey(20)
' Storing the secret key.
hfSecretKey.Value = secretKey
Dim encodedSecretKey As String = Me.ToBase32String(Encoding.UTF8.GetBytes(secretKey))
' Generate OAuthUri.
Dim otpAuthUri = String.Format("otpauth://totp/{0}:{1}?secret={2}&{3}&issuer={4}&algorithm={5}", issuer, account, encodedSecretKey.Trim("="c), secretKey, encodedIssuer, OtpHashMode.Sha1)
' Generate QR code image.
Using qrGenerator As QRCodeGenerator = New QRCodeGenerator()
Using qrCodeData As QRCodeData = qrGenerator.CreateQrCode(otpAuthUri, QRCodeGenerator.ECCLevel.Q)
Using qrCode As QRCode = New QRCode(qrCodeData)
Using qrCodeImage As Bitmap = qrCode.GetGraphic(20)
' Convert QR code image to byte array.
Using stream As MemoryStream = New MemoryStream()
qrCodeImage.Save(stream, ImageFormat.Png)
Dim bytes As Byte() = stream.ToArray()
Dim base64String = Convert.ToBase64String(bytes)
' Display QR code.
imgQRCode.ImageUrl = "data:image/png;base64," & base64String
End Using
End Using
End Using
End Using
End Using
End If
End Sub
Private Function GenerateRandomKey(length As Integer) As String
Using rng = New RNGCryptoServiceProvider()
Dim key = New Byte(length - 1) {}
rng.GetBytes(key)
Return Convert.ToBase64String(key)
End Using
End Function
Private Function ToBase32String(data As Byte()) As String
' Base32 character set.
Const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
Dim result As StringBuilder = New StringBuilder(Convert.ToInt32((data.Length * 8 + 4) / 5))
Dim buffer As Integer = data(0)
Dim [next] = 1
Dim bitsLeft = 8
While bitsLeft > 0 OrElse [next] < data.Length
If bitsLeft < 5 Then
If [next] < data.Length Then
buffer <<= 8
buffer = buffer Or data(Math.Min(Threading.Interlocked.Increment([next]), [next] - 1)) And &HFF
bitsLeft += 8
Else
Dim pad = 5 - bitsLeft
buffer <<= pad
bitsLeft += pad
End If
End If
Dim index = &H1F And buffer >> bitsLeft - 5
bitsLeft -= 5
result.Append(base32Chars(index))
End While
Return result.ToString()
End Function
Validating OTP generated from Microsoft Authenticator App
When the Validate OTP Button is clicked, the secret key is fetched from HiddenField and converted into BYTE array using GetBytes method of Encoding class.
Then, an instance of Totp class is created which accepts the following parameters.
Secret Key – BYTE array of secret key generated earlier in this code.
step – It defines the time interval within which the generated OTP will be valid. In this case it is 30 seconds.
mode – The OtpHashMode value is set to Sha1.
size – The length of the OTP to be generated. In this case it is 6.
Finally, the OTP is validated using the VerifyTotp method of Totp class and an appropriate message is displayed in the Label.
C#
protected void OnValidate(object sender, EventArgs e)
{
byte[] secretKeyBytes = Encoding.UTF8.GetBytes(hfSecretKey.Value);
// Validate the OTP.
Totp totp = new Totp(secretKeyBytes, step: 30, mode: OtpHashMode.Sha1, totpSize: 6);
bool isValid = totp.VerifyTotp(txtOTP.Text.Trim(), out long timeStepMatched, null);
// Display the validation result.
lblOTPValidation.Text = isValid ? "OTP is valid" : "OTP is invalid";
lblMessage.ForeColor = isValid ? Color.Green : Color.Red;
}
VB.Net
Protected Sub OnValidate(sender As Object, e As EventArgs)
Dim secretKeyBytes = Encoding.UTF8.GetBytes(hfSecretKey.Value)
' Validate the OTP.
Dim totp As Totp = New Totp(secretKeyBytes, [step]:=30, mode:=OtpHashMode.Sha1, totpSize:=6)
Dim timeStepMatched As Long = Nothing
Dim isValid As Boolean = totp.VerifyTotp(txtOTP.Text.Trim(), timeStepMatched, Nothing)
' Display the validation result.
lblMessage.Text = If(isValid, "OTP is valid", "OTP is invalid")
lblMessage.ForeColor = If(isValid, Color.Green, Color.Red)
End Sub
Scanning QR code and generating TOPT using the Microsoft Authenticator app
After installing the app, open it and click on Add account.
Then, choose the account type.
After that select Scan a QR code option.
Next, scan the QR code.
Once the account has been added, you will see your issuer name with account for which the OTP is generated.
Finally, open the account and you will see the generated One-time password code.
Note: The generated OTP is valid for 30 seconds and after 30 seconds a new OTP will be generated.
Enter the One-time password code in the TextBox and verify.
Screenshot
Downloads