You are browsing as a guest. Sign up (or log in) to start making projects!

1h 15m 57s logged

Devlog: Implementing 2FA (TOTP) Authentication in ASP.NET

This update focuses on authentication security. The core login flow has been refactored, and full support for Two-Factor Authentication (2FA) using authenticator apps (TOTP) has been implemented.

The entire feature has been split into a business logic layer (AuthService) and an API layer (AuthController) using ASP.NET Core Identity.


Architecture and Implementation Details

1. Updated Login Flow

The standard login mechanism now detects whether a user has enabled two-factor authentication. If 2FA is active, the API blocks immediate access and returns a response containing RequiresTwoFactor = true, signaling that further verification is required.

2. QR Code Secret Generation

To allow users to enable 2FA, the system securely generates a shared secret key and formats it into an industry-standard otpauth:// URI. This URI includes the issuer name (xbServerHub) and the user’s email, making it ready to be converted into a QR code on the frontend.

3. Input Sanitization

Users often format verification codes with spaces or hyphens. To prevent validation failures due to formatting, the service automatically sanitizes the input string using .Replace(" ", string.Empty).Replace("-", string.Empty) before passing it to the token provider.

4. Account Management Enforced by Authorization

New endpoints have been exposed to allow users to manually enable, verify, and disable their authenticator settings. All management endpoints are protected by the [Authorize] attribute, ensuring that only authenticated users can modify their security preferences.


Code Reference: Verification and Activation

The following method handles the final verification step. It cleans the user input, validates the token against the ASP.NET Identity token provider, and toggles the TwoFactorEnabled flag on the user record.

public async Task<VerifyAuthenticatorResponseDto> VerifyAuthenticatorAsync(string userId, string code)
{
    var user = await userManager.FindByIdAsync(userId);
    if (user is null)
    {
        return new()
        {
            IsSuccess = false,
            Message = "User not found"
        };
    }

    var sanitizedCode = code.Replace(" ", string.Empty).Replace("-", string.Empty);
    var isValid = await userManager.VerifyTwoFactorTokenAsync(user, userManager.Options.Tokens.AuthenticatorTokenProvider, sanitizedCode);

    if (!isValid)
    {
        _logger.LogWarning("Invalid authenticator code for user: {UserId}", userId);
        return new()
        {
            IsSuccess = false,
            Message = "Invalid authenticator code"
        };
    }

    var enableResult = await userManager.SetTwoFactorEnabledAsync(user, true);
    if (!enableResult.Succeeded)
    {
        _logger.LogWarning("Failed to enable 2FA for user {UserId}: {Errors}", userId,
            string.Join(", ", enableResult.Errors.Select(e => e.Description)));
        return new()
        {
            IsSuccess = false,
            Message = "Failed to enable 2FA"
        };
    }
    
    _logger.LogInformation("2FA enabled successfully for user: {UserId}", userId);
    return new()
    {
        IsSuccess = true,
        Message = "2FA enabled successfully"
    };
}
0
0

Comments 0

No comments yet. Be the first!