A Complete Step-by-Step Guide Using ASP.NET Core and Angular

Security is a critical part of any application. Passwords alone are no longer enough to protect user accounts because many users reuse passwords, pick weak ones, or get exposed through data leaks. Two-Factor Authentication (2FA) adds a powerful second layer of security, making unauthorized access significantly harder.

This article explains how to build a complete Two-Factor Authentication (2FA) system using:

We use simple language and provide full practical implementation.

What You Will Build

By the end of this tutorial, you will have:

  1. A login system that requires both password + 2FA code

  2. A QR code setup process for Google Authenticator

  3. Backend that generates and validates TOTP codes

  4. Database fields for storing 2FA secrets

  5. Angular UI for enabling and verifying 2FA

  6. A secure workflow similar to major applications (Google, GitHub, Microsoft)

Understanding Two-Factor Authentication (2FA)

2FA adds an extra security step after username and password:

  1. User enters email and password

  2. Backend verifies credentials

  3. Backend checks if 2FA is enabled

  4. If enabled → user must enter a 6-digit code

  5. Code changes every 30 seconds

  6. User gets code from an app like Google Authenticator

  7. Backend verifies the code

  8. User is authenticated

This prevents access even if the attacker knows the password.

Choosing the 2FA Method

This tutorial uses TOTP (Time-Based One-Time Password).
It is widely supported by:

TOTP uses:

This code is valid for 30 seconds.

PART 1: Backend (ASP.NET Core)

Step 1: Install Required Package

We use Otp.NET library for generating TOTP codes.

dotnet add package Otp.NET

Step 2: Update User Model

Models/User.cs

public class User
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public string Email { get; set; }
    public string PasswordHash { get; set; }

    public bool TwoFactorEnabled { get; set; } = false;
    public string TwoFactorSecretKey { get; set; }
}

Step 3: Generate Secret Key for User

When user enables 2FA:

using OtpNet;

public string Generate2FASecretKey()
{
    var bytes = KeyGeneration.GenerateRandomKey(20);
    return Base32Encoding.ToString(bytes);
}

Step 4: Create Endpoint to Enable 2FA

AuthController.cs

[Authorize]
[HttpPost("enable-2fa")]
public async Task<IActionResult> Enable2FA()
{
    var userEmail = User.FindFirstValue(ClaimTypes.Email);
    var user = await _context.Users.FirstOrDefaultAsync(u => u.Email == userEmail);

    if (user == null)
        return Unauthorized();

    var secretKey = Generate2FASecretKey();

    user.TwoFactorSecretKey = secretKey;
    await _context.SaveChangesAsync();

    var qrCodeUrl = $"otpauth://totp/{_config["App:Name"]}:{user.Email}?secret={secretKey}&issuer={_config["App:Name"]}&digits=6";

    return Ok(new { SecretKey = secretKey, QrCodeUrl = qrCodeUrl });
}

The response returns:

Angular will convert QR code URL into an actual QR image.

Step 5: Validate 2FA Code

[Authorize]
[HttpPost("verify-2fa")]
public async Task<IActionResult> Verify2FA([FromBody] TwoFactorDto dto)
{
    var userEmail = User.FindFirstValue(ClaimTypes.Email);
    var user = await _context.Users.FirstOrDefaultAsync(u => u.Email == userEmail);

    if (user == null)
        return Unauthorized();

    var totp = new Totp(Base32Encoding.ToBytes(user.TwoFactorSecretKey));
    var isValid = totp.VerifyTotp(dto.Code, out _);

    if (!isValid)
        return BadRequest(new { Message = "Invalid 2FA Code" });

    user.TwoFactorEnabled = true;
    await _context.SaveChangesAsync();

    return Ok(new { Message = "2FA Enabled Successfully" });
}

Step 6: Modify Login Endpoint to Require 2FA

if (user.TwoFactorEnabled)
{
    return Ok(new { Require2FA = true, Email = user.Email });
}

Step 7: Create Verify Login 2FA Endpoint

[HttpPost("login-2fa")]
public IActionResult Login2FA(VerifyLoginDto dto)
{
    var user = _context.Users.FirstOrDefault(x => x.Email == dto.Email);
    if (user == null) return BadRequest("Invalid attempt");

    var totp = new Totp(Base32Encoding.ToBytes(user.TwoFactorSecretKey));
    if (!totp.VerifyTotp(dto.Code, out _))
        return BadRequest("Invalid 2FA code");

    var token = _jwt.GenerateToken(user.Email);

    return Ok(new { Token = token });
}

Backend is ready.

PART 2: Frontend (Angular)

Step 1: Install QR Code Library

npm install angularx-qrcode

Step 2: Create Enable 2FA Component

ng generate component enable-2fa

Template

<h2>Enable Two-Factor Authentication</h2>

<button (click)="enable()">Generate QR Code</button>

<div *ngIf="qrCodeUrl">
  <qrcode [qrdata]="qrCodeUrl" [width]="200"></qrcode>
  <p>Secret Key: {{ secretKey }}</p>
</div>

<input type="text" placeholder="Enter 6 digit code" [(ngModel)]="code">

<button (click)="verify()">Verify</button>

<p>{{ message }}</p>

Component Script

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  templateUrl: './enable-2fa.component.html'
})
export class Enable2FAComponent {

  qrCodeUrl: string = "";
  secretKey: string = "";
  code: string = "";
  message: string = "";

  constructor(private http: HttpClient) {}

  enable() {
    this.http.post<any>("https://localhost:5001/api/auth/enable-2fa", {})
      .subscribe(res => {
        this.qrCodeUrl = res.qrCodeUrl;
        this.secretKey = res.secretKey;
      });
  }

  verify() {
    this.http.post<any>("https://localhost:5001/api/auth/verify-2fa", { code: this.code })
      .subscribe({
        next: res => this.message = res.message,
        error: err => this.message = "Invalid 2FA code"
      });
  }
}

Step 3: Modify Login Component

submit() {
  this.auth.login(this.model).subscribe(res => {
    if (res.require2FA) {
      this.needs2FA = true;
      this.emailFor2FA = res.email;
    } else {
      this.saveToken(res.token);
    }
  });
}

Step 4: Add Verify Login 2FA Step

verify2FA() {
  this.http.post<any>(this.apiUrl + "/login-2fa", {
    email: this.emailFor2FA,
    code: this.code
  })
  .subscribe(res => {
    this.auth.saveToken(res.token);
    this.router.navigate(['/dashboard']);
  });
}

Part 3: Complete Flow

The authentication process becomes:

Registration:

  1. User registers

  2. User logs in

  3. User visits Enable 2FA page

  4. User scans QR code

  5. User enters verification code

  6. 2FA is activated

Login

  1. User enters email + password

  2. Backend checks if 2FA is enabled

  3. If enabled → Angular asks for the verification code

  4. User enters 6-digit TOTP

  5. Backend validates code

  6. JWT token is issued

  7. User gets access

Security Benefits of 2FA

Two-Factor Authentication protects against:

Even if an attacker knows the password, they still cannot log in without the 2FA device.

Possible Extensions

I can help you add:

Just tell me what you want.

Conclusion

You now have a complete Two-Factor Authentication (2FA) system using:

This gives your application strong, professional-grade security.