ASP.NET Core  

Two-Factor Authentication (2FA) | A Complete Step-by-Step Guide Using ASP.NET Core and Angular

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:

  • ASP.NET Core Web API

  • SQL Server

  • Angular Frontend

  • Time-based One-Time Passwords (TOTP)

  • Google Authenticator / Microsoft Authenticator compatibility

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:

  • Google Authenticator

  • Authy

  • Microsoft Authenticator

  • 1Password

  • LastPass Authenticator

TOTP uses:

  • A shared secret key

  • The current timestamp

  • An algorithm to produce a 6-digit code

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:

  • SecretKey

  • QR code URL

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:

  • Stolen passwords

  • Credential stuffing attacks

  • Brute-force attacks

  • Phishing

  • Database leaks

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

Possible Extensions

I can help you add:

  • SMS-based 2FA

  • Email-based OTP

  • Backup recovery codes

  • Disable 2FA

  • Trusted devices system

  • Rate limiting 2FA attempts

  • Admin-only 2FA requirements

Just tell me what you want.

Conclusion

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

  • ASP.NET Core Web API

  • SQL Server

  • Angular

  • TOTP algorithm

  • Google Authenticator QR integration

  • Login workflow updates

  • Secure backend validation

This gives your application strong, professional-grade security.