TotpAuthSharp 2.1.0
See the version list below for details.
dotnet add package TotpAuthSharp --version 2.1.0
NuGet\Install-Package TotpAuthSharp -Version 2.1.0
<PackageReference Include="TotpAuthSharp" Version="2.1.0" />
<PackageVersion Include="TotpAuthSharp" Version="2.1.0" />
<PackageReference Include="TotpAuthSharp" />
paket add TotpAuthSharp --version 2.1.0
#r "nuget: TotpAuthSharp, 2.1.0"
#:package TotpAuthSharp@2.1.0
#addin nuget:?package=TotpAuthSharp&version=2.1.0
#tool nuget:?package=TotpAuthSharp&version=2.1.0
TotpAuthSharp
.net8.0 library for generating and validating timed based one time password authentication.
Based On Library
https://github.com/damirkusar/AspNetCore.Totp AspNetCore.Totp
What's New
2.1.0
- Choice of local QR generators. New
ZXingQrCodeGenerator(ZXing.Net) alongside the defaultSkiaQrCodeGenerator(SkiaSharp.QrCode). Both render on your server, so the shared secret never leaves it. - Verified QR accuracy. Each generator's output is scanned by the other library in the test suite (SkiaSharp.QrCode codes are read by ZXing, and ZXing codes by SkiaSharp.QrCode). The tests confirm the secret, issuer and account come back exactly, and that the scanned secret produces codes
TotpValidatoraccepts. - Linux and Alpine support out of the box. The package now includes SkiaSharp's Linux native library (
SkiaSharp.NativeAssets.Linux.NoDependencies). No extra packages or system libraries are needed, including onaspnet:8.0-alpine. - International issuer and account names fixed. Non-ASCII text such as "Café" or "Zoë Müller" is now encoded correctly (UTF-8), so it displays properly in authenticator apps.
- Special characters in account names fixed. Characters such as
:,?,#,/and@in the account name are now encoded instead of corrupting the QR payload. - Non-square QR sizes fixed. A
qrCodeWidthdifferent fromqrCodeHeightused to stretch the code so it could not be scanned reliably. The code is now drawn square and centred. Square sizes, including the 300x300 default, are unchanged. GenerateFromWebis obsolete. It sends the shared secret to quickchart.io. UseGenerateinstead.GenerateFromWebstill works but will be removed in 3.0.- Dependencies: SkiaSharp.QrCode upgraded from 1.0.0 to 1.2.0 (faster encoding; fixes a binary incompatibility with apps that use SkiaSharp.QrCode 1.1 or later). ZXing.Net 0.16.11 and ZXing.Net.Bindings.SkiaSharp 0.16.24 added.
Upgrading from 2.0.x
- Calls to
GenerateFromWebnow produce an obsolete warning (CS0618). Projects that treat warnings as errors must switch toGenerate. - Account names are now percent-encoded in the QR payload, so
jane@example.comis written asjane%40example.com. This follows the otpauth URL format, and authenticator apps decode it for display. Users who have already enrolled are unaffected, because the QR code is only used during setup.
2.0.0
- Upgraded to SkiaSharp.QrCode 1.0.0.
- Added
IQrCodeGeneratorandIQrCodeDownloaderso QR generation can be injected or mocked.
Getting Started
Installing the package
Open up an existing project, or create a new one. Add a reference to the TotpAuthSharp library.
.NET Core CLI
dotnet add package TotpAuthSharp
PowerShell (Nuget Package Manager)
Install-Package TotpAuthSharp
Manual entry (.csproj)
<Project Sdk="Microsoft.NET.Sdk.Web">
...
<ItemGroup>
<PackageReference Include="TotpAuthSharp" Version="x.x.x" />
</ItemGroup>
</Project>
Platform support
The package targets .NET 8 and runs on Windows, macOS and Linux. Linux support includes Alpine (musl) and Debian/Ubuntu (glibc) on x64 and ARM64.
No extra NuGet packages or system libraries are needed. The Linux native library is included and does not require libfontconfig. Version 2.1.0 was tested on the mcr.microsoft.com/dotnet/aspnet:8.0-alpine, sdk:8.0-alpine and sdk:8.0 images.
Public Namespace Structure
TotpAuthSharp
CLASSTotpGeneratorCLASSTotpValidatorCLASSTotpSetupGeneratorTotpAuthSharp.Helper
CLASSBase32CLASSGuardCLASSSkiaQrCodeGenerator (implementsIQrCodeGenerator)CLASSZXingQrCodeGenerator (implementsIQrCodeGenerator)CLASSHttpQrCodeDownloader (implementsIQrCodeDownloader)CLASSTotpHasherCLASSUrlEncoder
TotpAuthSharp.Models
CLASSTotpSetupCLASSQrCodeImage
TotpAuthSharp.Interface
INTERFACEIQrCodeImageINTERFACEIQrCodeGeneratorINTERFACEIQrCodeDownloaderINTERFACEITotpGeneratorINTERFACEITotpSetupINTERFACEITotpSetupGeneratorINTERFACEITotpValidator
Using the package
Class: TotpGenerator
Constructor Parameters: None
Description: Used for generating the TOTP code, using a super secret code for your app.
Example
var generator = new TotpGenerator();
var code = generator.Generate(_userIdentity.AccountSecretKey);
TotpValidator
Constructor Parameters: TotpGenerator
Description: Generates a new token and compares against a given TOTP code to check validity.
Example
var generator = new TotpGenerator();
var validator = new TotpValidator(generator);
var code = validator.Validate(_userIdentity.AccountSecretKey, code);
TotpSetupGenerator
Constructor Parameters: None (default), or IQrCodeGenerator, IQrCodeDownloader for custom composition / testing
Description: Generates the setup details a user needs to add your app to an authenticator app (Google Authenticator, Microsoft Authenticator and so on). It returns a TotpSetup containing the QR code image (PNG bytes and a data: URI) and the manual setup key.
The parameterless constructor wires the default SkiaQrCodeGenerator (local QR generation via SkiaSharp.QrCode) and HttpQrCodeDownloader (used only by the obsolete GenerateFromWeb). A second constructor accepts these dependencies so you can inject your own implementations or mocks:
// Default
var qrGenerator = new TotpSetupGenerator();
// Injected (IoC / testing)
var qrGenerator = new TotpSetupGenerator(myQrCodeGenerator, myQrCodeDownloader);
// Local generation with ZXing.Net instead of SkiaSharp.QrCode (using TotpAuthSharp.Helper;)
var qrGenerator = new TotpSetupGenerator(new ZXingQrCodeGenerator(), new HttpQrCodeDownloader());
Choosing a QR generator
| Generator | Library | Notes |
|---|---|---|
SkiaQrCodeGenerator |
SkiaSharp.QrCode | Default. |
ZXingQrCodeGenerator |
ZXing.Net | Error correction level M. |
Both render the QR code locally, so the shared secret never leaves your server. Both produce a PNG of the requested size and work on every supported platform. The test suite checks each one against the other library's decoder. You can also supply your own implementation of IQrCodeGenerator.
Generate (recommended)
Renders the QR code locally with the configured IQrCodeGenerator.
issueris written to the otpauthissuerparameter, percent-encoded as UTF-8, so spaces and non-ASCII text are kept (for example "TACS UAT").accountIdentityhas its spaces removed, then is percent-encoded.qrCodeWidthandqrCodeHeightdefault to 300px. If they differ, the code is drawn square at the smaller size and centred.
Example
var qrGenerator = new TotpSetupGenerator();
var qrCode = qrGenerator.Generate(
issuer: "TestCo",
accountIdentity: _userIdentity.Id.ToString(),
accountSecretKey: _userIdentity.AccountSecretKey
);
GenerateFromWeb (obsolete)
Description: Fetches the QR code image from quickchart.io and returns it as a TotpSetup class containing the image.
Obsolete since 2.1.0:
GenerateFromWebsends the whole otpauth URL, including the shared secret, to quickchart.io. UseGenerate, which renders locally.GenerateFromWebwill be removed in 3.0.
Example
var qrGenerator = new TotpSetupGenerator();
var qrCode = qrGenerator.GenerateFromWeb(
issuer: "TestCo",
accountIdentity: _userIdentity.Id.ToString(),
accountSecretKey: _userIdentity.AccountSecretKey
);
Usage Samples
1. Register the services (ASP.NET Core dependency injection)
All the classes are stateless, so singletons are safe.
using TotpAuthSharp;
using TotpAuthSharp.Helper;
using TotpAuthSharp.Interface;
builder.Services.AddSingleton<ITotpGenerator, TotpGenerator>();
builder.Services.AddSingleton<ITotpValidator, TotpValidator>();
// Pick the local QR generator: SkiaQrCodeGenerator (default) or ZXingQrCodeGenerator.
builder.Services.AddSingleton<IQrCodeGenerator, ZXingQrCodeGenerator>();
// Register TotpSetupGenerator with a factory so it uses the generator above.
builder.Services.AddSingleton<ITotpSetupGenerator>(sp =>
new TotpSetupGenerator(sp.GetRequiredService<IQrCodeGenerator>(), new HttpQrCodeDownloader()));
Why the factory?
TotpSetupGeneratorhas a parameterless constructor. If you register it withAddSingleton<ITotpSetupGenerator, TotpSetupGenerator>()without also registeringIQrCodeDownloader, the container picks the parameterless constructor and silently usesSkiaQrCodeGenerator, ignoring yourIQrCodeGeneratorregistration.
2. Enrol a user
Create a random secret per user and store it with the user record. Treat it like a password: encrypt it at rest and never log it. Then return the QR code and the manual setup key.
using System.Security.Cryptography;
app.MapPost("/2fa/setup", (ITotpSetupGenerator setupGenerator) =>
{
var accountSecretKey = Convert.ToBase64String(RandomNumberGenerator.GetBytes(20));
// Save accountSecretKey against the user here (encrypted), marked as "not yet confirmed".
var setup = setupGenerator.Generate(
issuer: "TACS UAT",
accountIdentity: "jane.doe@example.co.za",
accountSecretKey: accountSecretKey);
return Results.Ok(new
{
qrCodeImage = setup.QrCodeImage, // data:image/png;base64,... ready for an <img> tag
manualSetupKey = setup.ManualSetupKey // for users who cannot scan
});
});
3. Confirm enrolment with the first code
Only switch two-factor authentication on once the user proves their app works. This catches a mistyped manual key or a failed scan.
app.MapPost("/2fa/confirm", (ITotpValidator validator, ConfirmRequest request) =>
{
var accountSecretKey = "..."; // load the unconfirmed secret for the current user
if (!validator.Validate(accountSecretKey, request.Code))
return Results.BadRequest("That code did not match. Check your device clock and try again.");
// Mark two-factor authentication as enabled for the user here.
return Results.Ok();
});
public record ConfirmRequest(int Code);
4. Show the QR code
As a data: URI in a Razor page or view, with no extra endpoint needed:
<img src="@Model.QrCodeImage" width="300" height="300" alt="Scan this QR code with your authenticator app" />
<p>Can't scan it? Enter this key manually: <code>@Model.ManualSetupKey</code></p>
Or as a PNG from an endpoint:
app.MapGet("/2fa/qr.png", (ITotpSetupGenerator setupGenerator) =>
{
var setup = setupGenerator.Generate("TACS UAT", "jane.doe@example.co.za", "the user's stored secret");
return Results.File(setup.QrCodeImageBytes, "image/png");
});
5. Validate a code at sign-in
app.MapPost("/2fa/verify", (ITotpValidator validator, ConfirmRequest request) =>
{
var accountSecretKey = "..."; // load the confirmed secret for the current user
// The third argument is the allowed clock drift in seconds (default 60).
return validator.Validate(accountSecretKey, request.Code, timeToleranceInSeconds: 30)
? Results.Ok()
: Results.Unauthorized();
});
6. Without dependency injection (console app or script)
using TotpAuthSharp;
using TotpAuthSharp.Helper;
var setupGenerator = new TotpSetupGenerator(new ZXingQrCodeGenerator(), new HttpQrCodeDownloader());
var setup = setupGenerator.Generate("TACS UAT", "Jane Doe", "the user's secret", qrCodeWidth: 400, qrCodeHeight: 400);
File.WriteAllBytes("totp-qr.png", setup.QrCodeImageBytes);
Console.WriteLine($"Manual setup key: {setup.ManualSetupKey}");
7. Unit testing your code
Every service has an interface, so you can mock it (example uses Moq):
var validator = new Mock<ITotpValidator>();
validator.Setup(v => v.Validate(It.IsAny<string>(), 123456, It.IsAny<int>())).Returns(true);
var qrCodeGenerator = new Mock<IQrCodeGenerator>();
qrCodeGenerator.Setup(g => g.Generate(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
.Returns(new byte[] { 1, 2, 3 });
var setupGenerator = new TotpSetupGenerator(qrCodeGenerator.Object, Mock.Of<IQrCodeDownloader>());
Example Implementation
using System;
using TotpAuthSharp;
using Microsoft.AspNetCore.Mvc;
namespace AuthApi.Controllers
{
internal struct UserIdentity
{
public int Id { get; set; }
public string AccountSecretKey { get; set; }
}
internal static class AuthProvider
{
public static UserIdentity GetUserIdentity()
{
return new UserIdentity()
{
Id = new Random().Next(0, 999),
AccountSecretKey = Guid.NewGuid().ToString()
};
}
}
[ApiController]
[Route("[controller]")]
public class TotpController : ControllerBase
{
private readonly ITotpGenerator _totpGenerator;
private readonly ITotpSetupGenerator _totpQrGenerator;
private readonly ITotpValidator _totpValidator;
private readonly UserIdentity _userIdentity;
public TotpController()
{
_totpGenerator = new TotpGenerator();
_totpValidator = new TotpValidator(_totpGenerator);
_totpQrGenerator = new TotpSetupGenerator();
_userIdentity = AuthProvider.GetUserIdentity();
}
[HttpGet("code")]
public int GetCode()
{
return _totpGenerator.Generate(_userIdentity.AccountSecretKey);
}
[HttpGet("qr-code")]
public IActionResult GetQr()
{
var qrCode = _totpQrGenerator.Generate(
"TestCo",
_userIdentity.Id.ToString(),
_userIdentity.AccountSecretKey
);
return File(qrCode.QrCodeImageBytes, "image/png");
}
[HttpPost("validate")]
public bool Validate([FromBody] int code)
{
return _totpValidator.Validate(_userIdentity.AccountSecretKey, code);
}
}
}
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- SkiaSharp.NativeAssets.Linux.NoDependencies (>= 4.151.1)
- SkiaSharp.QrCode (>= 1.2.0)
- ZXing.Net (>= 0.16.11)
- ZXing.Net.Bindings.SkiaSharp (>= 0.16.24)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.