diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/TfaRegistrationController.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/TfaRegistrationController.cs
new file mode 100644
index 00000000..607b6c8e
--- /dev/null
+++ b/EnvelopeGenerator.GeneratorAPI/Controllers/TfaRegistrationController.cs
@@ -0,0 +1,130 @@
+using DigitalData.Core.Abstraction.Application.DTO;
+using EnvelopeGenerator.Application.Common.Extensions;
+using EnvelopeGenerator.Application.Common.Interfaces.Services;
+using EnvelopeGenerator.Application.Resources;
+using EnvelopeGenerator.Domain.Constants;
+using EnvelopeGenerator.GeneratorAPI.Extensions;
+using EnvelopeGenerator.GeneratorAPI.Models;
+using Ganss.Xss;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Options;
+
+namespace EnvelopeGenerator.GeneratorAPI.Controllers;
+
+///
+/// Exposes endpoints for registering and managing two-factor authentication for envelope receivers.
+///
+[ApiController]
+[Route("api/tfa")]
+public class TfaRegistrationController : ControllerBase
+{
+ private readonly ILogger _logger;
+ private readonly IEnvelopeReceiverService _envelopeReceiverService;
+ private readonly IAuthenticator _authenticator;
+ private readonly IReceiverService _receiverService;
+ private readonly TFARegParams _parameters;
+ private readonly IStringLocalizer _localizer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TfaRegistrationController(
+ ILogger logger,
+ IEnvelopeReceiverService envelopeReceiverService,
+ IAuthenticator authenticator,
+ IReceiverService receiverService,
+ IOptions tfaRegParamsOptions,
+ IStringLocalizer localizer)
+ {
+ _logger = logger;
+ _envelopeReceiverService = envelopeReceiverService;
+ _authenticator = authenticator;
+ _receiverService = receiverService;
+ _parameters = tfaRegParamsOptions.Value;
+ _localizer = localizer;
+ }
+
+ ///
+ /// Generates registration metadata (QR code and deadline) for a receiver.
+ ///
+ /// Encoded envelope receiver id.
+ [Authorize]
+ [HttpGet("{envelopeReceiverId}")]
+ public async Task RegisterAsync(string envelopeReceiverId)
+ {
+ try
+ {
+ var (uuid, signature) = envelopeReceiverId.DecodeEnvelopeReceiverId();
+
+ if (uuid is null || signature is null)
+ {
+ _logger.LogEnvelopeError(uuid: uuid, signature: signature, message: _localizer.WrongEnvelopeReceiverId());
+ return Unauthorized(new { message = _localizer.WrongEnvelopeReceiverId() });
+ }
+
+ var secretResult = await _envelopeReceiverService.ReadWithSecretByUuidSignatureAsync(uuid: uuid, signature: signature);
+ if (secretResult.IsFailed)
+ {
+ _logger.LogNotice(secretResult.Notices);
+ return NotFound(new { message = _localizer.WrongEnvelopeReceiverId() });
+ }
+
+ var envelopeReceiver = secretResult.Data;
+
+ if (!envelopeReceiver.Envelope!.TFAEnabled)
+ return Unauthorized(new { message = _localizer.WrongAccessCode() });
+
+ var receiver = envelopeReceiver.Receiver;
+ receiver!.TotpSecretkey = _authenticator.GenerateTotpSecretKey();
+ await _receiverService.UpdateAsync(receiver);
+ var totpQr64 = _authenticator.GenerateTotpQrCode(userEmail: receiver.EmailAddress, secretKey: receiver.TotpSecretkey).ToBase64String();
+
+ if (receiver.TfaRegDeadline is null)
+ {
+ receiver.TfaRegDeadline = _parameters.Deadline;
+ await _receiverService.UpdateAsync(receiver);
+ }
+ else if (receiver.TfaRegDeadline <= DateTime.Now)
+ {
+ return StatusCode(StatusCodes.Status410Gone, new { message = _localizer.WrongAccessCode() });
+ }
+
+ return Ok(new
+ {
+ envelopeReceiver.EnvelopeId,
+ envelopeReceiver.Envelope!.Uuid,
+ envelopeReceiver.Receiver!.Signature,
+ receiver.TfaRegDeadline,
+ TotpQR64 = totpQr64
+ });
+ }
+ catch (Exception ex)
+ {
+ _logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, exception: ex, message: _localizer.WrongEnvelopeReceiverId());
+ return StatusCode(StatusCodes.Status500InternalServerError, new { message = _localizer.UnexpectedError() });
+ }
+ }
+
+ ///
+ /// Logs out the envelope receiver from cookie authentication.
+ ///
+ [Authorize(Roles = ReceiverRole.FullyAuth)]
+ [HttpPost("auth/logout")]
+ public async Task LogOutAsync()
+ {
+ try
+ {
+ await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
+ return Ok();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "{message}", ex.Message);
+ return StatusCode(StatusCodes.Status500InternalServerError, new { message = _localizer.UnexpectedError() });
+ }
+ }
+}