C# / .NET

Add 2FA to Your C# Application

A complete guide to integrating Google Authenticator using AuthenticatorAPI.com

Also available in: PHP Python JavaScript Java

This guide shows how to integrate Google Authenticator-compatible 2FA into C# applications — covering ASP.NET Core MVC, ASP.NET Web Forms, and standalone console/service applications. Uses only HttpClient from the BCL, with no NuGet packages required.

👉 Compatibility: .NET 6, 7, 8 (modern), and .NET Framework 4.6.1+ (legacy). All examples use HttpClient with async/await.
Step-by-Step Integration
1

Create a TotpService Helper Class

Encapsulate all calls to the API in a single service class. Register it with the DI container as a singleton (sharing a single HttpClient instance is the .NET best practice).

using System.Net.Http;
using System.Security.Cryptography;
using System.Web;

public class TotpService
{
    private const string Base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    private const string ApiBase = "https://www.authenticatorApi.com";

    private readonly HttpClient _http;

    public TotpService(HttpClient http) => _http = http;

    /// <summary>Generates a cryptographically random 32-character Base32 secret.</summary>
    public string GenerateSecret(int length = 32)
    {
        var bytes = RandomNumberGenerator.GetBytes(length);
        return new string(bytes.Select(b => Base32Alphabet[b % 32]).ToArray());
    }

    /// <summary>Returns the HTML img tag QR code for the setup page.</summary>
    public async Task<string> GetQrCodeHtmlAsync(string appName, string userInfo, string secret)
    {
        var url = $"{ApiBase}/pair.aspx"
            + $"?AppName={HttpUtility.UrlEncode(appName)}"
            + $"&AppInfo={HttpUtility.UrlEncode(userInfo)}"
            + $"&SecretCode={HttpUtility.UrlEncode(secret)}";

        return await _http.GetStringAsync(url);
    }

    /// <summary>Returns true if the PIN is valid for the given secret.</summary>
    public async Task<bool> ValidatePinAsync(string pin, string secret)
    {
        if (!pin.All(char.IsDigit) || pin.Length != 6) return false;

        var url = $"{ApiBase}/Validate.aspx"
            + $"?Pin={pin}"
            + $"&SecretCode={HttpUtility.UrlEncode(secret)}";

        var response = await _http.GetStringAsync(url);
        return string.Equals(response.Trim(), "True", StringComparison.OrdinalIgnoreCase);
    }
}
2

Register with Dependency Injection

// Program.cs
builder.Services.AddHttpClient<TotpService>(client =>
{
    client.Timeout = TimeSpan.FromSeconds(10);
});

// Or as a plain singleton if not using typed HttpClient:
builder.Services.AddSingleton<TotpService>(sp =>
    new TotpService(sp.GetRequiredService<IHttpClientFactory>().CreateClient()));
3

Enrolment Controller (ASP.NET Core MVC)

public class TwoFactorController : Controller
{
    private readonly TotpService _totp;
    private readonly IUserRepository _users;

    public TwoFactorController(TotpService totp, IUserRepository users)
    {
        _totp  = totp;
        _users = users;
    }

    [HttpGet]
    public async Task<IActionResult> Setup()
    {
        var secret = _totp.GenerateSecret();
        HttpContext.Session.SetString("Pending2FASecret", secret);

        var qrHtml = await _totp.GetQrCodeHtmlAsync(
            "MyApp",
            User.Identity!.Name!,
            secret);

        ViewBag.QrHtml = qrHtml;
        ViewBag.Secret  = secret;
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> ConfirmSetup(string pin)
    {
        var secret = HttpContext.Session.GetString("Pending2FASecret");
        if (await _totp.ValidatePinAsync(pin, secret!))
        {
            await _users.SaveTotpSecretAsync(User.Identity!.Name!, secret!);
            return RedirectToAction("Index", "Home");
        }
        ModelState.AddModelError("", "Invalid code — please try again.");
        return View("Setup");
    }

    [HttpPost]
    public async Task<IActionResult> Verify(string pin)
    {
        var secret = await _users.GetTotpSecretAsync(User.Identity!.Name!);
        if (await _totp.ValidatePinAsync(pin, secret))
        {
            HttpContext.Session.SetString("2FAVerified", "true");
            return RedirectToAction("Dashboard");
        }
        ModelState.AddModelError("", "Invalid code — please try again.");
        return View();
    }
}
1

.NET Framework 4.x — Helper Class

using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Web;

public static class TotpHelper
{
    private static readonly HttpClient _http = new HttpClient();
    private const string Base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

    public static string GenerateSecret()
    {
        var rng   = new RNGCryptoServiceProvider();
        var bytes = new byte[32];
        rng.GetBytes(bytes);
        var sb = new System.Text.StringBuilder();
        foreach (var b in bytes) sb.Append(Base32Alphabet[b % 32]);
        return sb.ToString();
    }

    public static string GetQrCodeHtml(string appName, string userInfo, string secret)
    {
        var url = $"https://www.authenticatorApi.com/pair.aspx"
            + $"?AppName={HttpUtility.UrlEncode(appName)}"
            + $"&AppInfo={HttpUtility.UrlEncode(userInfo)}"
            + $"&SecretCode={HttpUtility.UrlEncode(secret)}";
        return _http.GetStringAsync(url).Result;
    }

    public static bool ValidatePin(string pin, string secret)
    {
        var url = $"https://www.authenticatorApi.com/Validate.aspx?Pin={pin}&SecretCode={HttpUtility.UrlEncode(secret)}";
        var result = _http.GetStringAsync(url).Result.Trim();
        return string.Equals(result, "True", StringComparison.OrdinalIgnoreCase);
    }
}
2

ASP.NET Web Forms Usage

// Setup.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        var secret = TotpHelper.GenerateSecret();
        Session["Pending2FASecret"] = secret;
        QrCodeLiteral.Text = TotpHelper.GetQrCodeHtml("MyApp", User.Identity.Name, secret);
        SecretLabel.Text = secret;
    }
}

protected void btnVerify_Click(object sender, EventArgs e)
{
    var secret = Session["Pending2FASecret"] as string;
    if (TotpHelper.ValidatePin(PinTextBox.Text.Trim(), secret))
    {
        // Save secret to user's profile and redirect
        SaveSecretToProfile(secret);
        Response.Redirect("~/Default.aspx");
    }
    else
    {
        ErrorLabel.Text = "Invalid code — please try again.";
        ErrorLabel.Visible = true;
    }
}
Security Checklist for C#