🔐 Security✍️ Khoa📅 20/04/2026☕ 12 phút đọc

Advanced Authentication: MFA, SSO & Zero Trust

Khi authentication cơ bản (username/password) không đủ an toàn, chúng ta cần các phương pháp nâng cao hơn: Multi-Factor Authentication (MFA), Single Sign-On (SSO), và Zero Trust Architecture. Đây là những gì enterprise systems và security-critical apps sử dụng.

🔐 Ẩn dụ: Password là chìa khóa nhà. MFA là thêm vân tay + mã PIN. SSO là master key mở nhiều cửa. Zero Trust là "kiểm tra mọi người ở mọi cửa, mọi lúc".


Multi-Factor Authentication (MFA)

MFA yêu cầu user chứng minh identity bằng 2 hoặc nhiều factors từ 3 categories:

┌─────────────────────────────────────────────────┐
│  1. Something you KNOW     (password, PIN)      │
│  2. Something you HAVE     (phone, hardware key)│
│  3. Something you ARE      (fingerprint, face)  │
└─────────────────────────────────────────────────┘

Ví dụ MFA:

  • Password (know) + SMS code (have)
  • Password (know) + Fingerprint (are)
  • Hardware key (have) + PIN (know)

TOTP (Time-based One-Time Password)

TOTP là dạng MFA phổ biến nhất (Google Authenticator, Authy, 1Password).

How TOTP works

┌────────────┐                              ┌────────────┐
│   Server   │                              │   Client   │
│            │                              │ (Auth app) │
└─────┬──────┘                              └─────┬──────┘
      │                                           │
      │ 1. User enrolls MFA                       │
      │    Generate secret key (32 bytes)         │
      │                                           │
      │ 2. Show QR code with secret               │
      ├──────────────────────────────────────────►│
      │    otpauth://totp/App:user@example.com    │
      │    ?secret=BASE32_ENCODED_SECRET          │
      │                                           │
      │                         3. Client scans QR & stores secret
      │                            HMAC-SHA1(secret, time_counter)
      │                            → 6-digit code (changes every 30s)
      │                                           │
      │ 4. User enters code: 123456               │
      │◄──────────────────────────────────────────┤
      │                                           │
      │ 5. Server computes:                       │
      │    current_code = HMAC(secret, current_time/30)
      │    Verify user's code matches             │
      │    (allow ±1 time window for clock skew)  │
      │                                           │
      │ 6. Grant access if codes match            │
      │                                           │

Implementation (Go)

import (
    "crypto/rand"
    "encoding/base32"
    "fmt"

    "github.com/pquerna/otp"
    "github.com/pquerna/otp/totp"
)

// Generate TOTP secret for new user
func generateTOTPSecret(issuer, accountName string) (*otp.Key, error) {
    key, err := totp.Generate(totp.GenerateOpts{
        Issuer:      issuer,      // "MyApp"
        AccountName: accountName, // "user@example.com"
        Period:      30,          // New code every 30 seconds
        Digits:      6,           // 6-digit code
    })
    return key, err
}

// Enroll MFA
func handleMFAEnroll(w http.ResponseWriter, r *http.Request) {
    userID := getUserIDFromSession(r)
    email := getEmailFromSession(r)

    // Generate secret
    key, err := generateTOTPSecret("MyApp", email)
    if err != nil {
        http.Error(w, "Failed to generate secret", 500)
        return
    }

    // Store secret in DB (encrypted!)
    encryptedSecret := encrypt(key.Secret())
    db.Exec("UPDATE users SET totp_secret = ? WHERE id = ?", encryptedSecret, userID)

    // Return QR code to user
    qrCode := key.String() // otpauth://totp/...
    // Generate QR image from qrCode string (use library like github.com/skip2/go-qrcode)

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{
        "qr_code": qrCode,
        "secret":  key.Secret(), // Show in case user can't scan QR
    })
}

// Verify TOTP code during login
func verifyTOTP(secret, code string) bool {
    return totp.Validate(code, secret)
}

// Login flow with MFA
func handleLoginWithMFA(w http.ResponseWriter, r *http.Request) {
    email := r.FormValue("email")
    password := r.FormValue("password")
    totpCode := r.FormValue("totp_code")

    // Step 1: Verify password
    user, err := db.GetUserByEmail(email)
    if err != nil || !checkPassword(password, user.PasswordHash) {
        http.Error(w, "Invalid credentials", 401)
        return
    }

    // Step 2: If MFA enabled, verify TOTP
    if user.TOTPSecret != "" {
        decryptedSecret := decrypt(user.TOTPSecret)
        
        if !verifyTOTP(decryptedSecret, totpCode) {
            http.Error(w, "Invalid MFA code", 401)
            return
        }
    }

    // Step 3: Create session
    createSession(w, user.ID)
    w.WriteHeader(200)
}

// Generate backup codes (in case user loses device)
func generateBackupCodes(userID int64) []string {
    codes := make([]string, 10)
    for i := range codes {
        b := make([]byte, 4)
        rand.Read(b)
        codes[i] = fmt.Sprintf("%08X", b)
    }

    // Store hashed backup codes in DB
    for _, code := range codes {
        hashedCode := hashPassword(code)
        db.Exec("INSERT INTO backup_codes (user_id, code_hash) VALUES (?, ?)", userID, hashedCode)
    }

    return codes // Show to user once, tell them to save
}

// Verify backup code
func verifyBackupCode(userID int64, code string) bool {
    var codeHash string
    err := db.QueryRow("SELECT code_hash FROM backup_codes WHERE user_id = ? AND used = false", userID).Scan(&codeHash)
    if err != nil {
        return false
    }

    if !checkPassword(code, codeHash) {
        return false
    }

    // Mark as used (one-time use)
    db.Exec("UPDATE backup_codes SET used = true WHERE code_hash = ?", codeHash)
    return true
}

Security Best Practices for TOTP

Store secret encrypted in database
Generate backup codes (10x codes for recovery)
Allow ±1 time window (to handle clock skew)
Rate limit verification attempts (max 5 per minute)
Require re-authentication before disabling MFA


WebAuthn / FIDO2 (Passwordless)

WebAuthn là standard mới nhất cho passwordless authentication sử dụng hardware keys (YubiKey) hoặc biometrics (Touch ID, Face ID).

How WebAuthn works

┌─────────────────────────────────────────────────────────┐
│  Registration Flow                                      │
└─────────────────────────────────────────────────────────┘

Browser                 Server                Authenticator (YubiKey)
   │                      │                           │
   │ 1. POST /register    │                           │
   ├─────────────────────►│                           │
   │                      │ 2. Generate challenge     │
   │                      │    (random bytes)         │
   │ 3. Challenge         │                           │
   │◄─────────────────────┤                           │
   │                      │                           │
   │ 4. navigator.credentials.create()                │
   ├──────────────────────────────────────────────────►
   │                      │    5. User touches key    │
   │                      │       Generate key pair   │
   │                      │       Sign challenge      │
   │ 6. Public key + signature                        │
   │◄──────────────────────────────────────────────────┤
   │                      │                           │
   │ 7. POST public key   │                           │
   ├─────────────────────►│                           │
   │                      │ 8. Store public key in DB │
   │                      │                           │
   │ 9. Success           │                           │
   │◄─────────────────────┤                           │
   │                      │                           │

┌─────────────────────────────────────────────────────────┐
│  Authentication Flow                                    │
└─────────────────────────────────────────────────────────┘

   │ 1. POST /login       │                           │
   ├─────────────────────►│                           │
   │                      │ 2. Generate challenge     │
   │ 3. Challenge         │                           │
   │◄─────────────────────┤                           │
   │                      │                           │
   │ 4. navigator.credentials.get()                   │
   ├──────────────────────────────────────────────────►
   │                      │    5. User touches key    │
   │                      │       Sign with private key
   │ 6. Signature         │                           │
   │◄──────────────────────────────────────────────────┤
   │                      │                           │
   │ 7. POST signature    │                           │
   ├─────────────────────►│                           │
   │                      │ 8. Verify with public key │
   │                      │    (from DB)              │
   │ 9. Token             │                           │
   │◄─────────────────────┤                           │

Implementation (Go)

import "github.com/go-webauthn/webauthn/webauthn"

var (
    webAuthn *webauthn.WebAuthn
)

func init() {
    wconfig := &webauthn.Config{
        RPDisplayName: "My App",
        RPID:          "example.com",
        RPOrigin:      "https://example.com",
    }
    webAuthn, _ = webauthn.New(wconfig)
}

// Begin registration
func handleBeginRegistration(w http.ResponseWriter, r *http.Request) {
    user := getUserFromSession(r) // Implement WebAuthnUser interface

    options, session, err := webAuthn.BeginRegistration(user)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    // Store session (contains challenge)
    storeSession("webauthn_session", session)

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(options)
}

// Finish registration
func handleFinishRegistration(w http.ResponseWriter, r *http.Request) {
    user := getUserFromSession(r)
    session := getStoredSession("webauthn_session")

    credential, err := webAuthn.FinishRegistration(user, session, r)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    // Store credential in DB
    db.Exec("INSERT INTO webauthn_credentials (user_id, credential_id, public_key) VALUES (?, ?, ?)", 
        user.ID, credential.ID, credential.PublicKey)

    w.WriteHeader(200)
}

// Begin login
func handleBeginLogin(w http.ResponseWriter, r *http.Request) {
    user := getUserByUsername(r.FormValue("username"))

    options, session, err := webAuthn.BeginLogin(user)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    storeSession("webauthn_session", session)

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(options)
}

// Finish login
func handleFinishLogin(w http.ResponseWriter, r *http.Request) {
    user := getUserFromSession(r)
    session := getStoredSession("webauthn_session")

    _, err := webAuthn.FinishLogin(user, session, r)
    if err != nil {
        http.Error(w, "Authentication failed", 401)
        return
    }

    // Create session token
    createSession(w, user.ID)
    w.WriteHeader(200)
}

Frontend (JavaScript)

// Registration
async function registerWebAuthn() {
  // Get options from server
  const optionsRes = await fetch('/webauthn/register/begin', {method: 'POST'});
  const options = await optionsRes.json();

  // Convert base64 to ArrayBuffer (needed for WebAuthn API)
  options.publicKey.challenge = base64ToArrayBuffer(options.publicKey.challenge);
  options.publicKey.user.id = base64ToArrayBuffer(options.publicKey.user.id);

  // Create credential (triggers browser prompt / hardware key)
  const credential = await navigator.credentials.create(options);

  // Send to server
  await fetch('/webauthn/register/finish', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
      id: credential.id,
      rawId: arrayBufferToBase64(credential.rawId),
      response: {
        attestationObject: arrayBufferToBase64(credential.response.attestationObject),
        clientDataJSON: arrayBufferToBase64(credential.response.clientDataJSON),
      },
      type: credential.type,
    }),
  });
}

// Login
async function loginWebAuthn() {
  const optionsRes = await fetch('/webauthn/login/begin', {
    method: 'POST',
    body: JSON.stringify({username: 'alice'}),
  });
  const options = await optionsRes.json();

  options.publicKey.challenge = base64ToArrayBuffer(options.publicKey.challenge);

  const assertion = await navigator.credentials.get(options);

  await fetch('/webauthn/login/finish', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
      id: assertion.id,
      rawId: arrayBufferToBase64(assertion.rawId),
      response: {
        authenticatorData: arrayBufferToBase64(assertion.response.authenticatorData),
        clientDataJSON: arrayBufferToBase64(assertion.response.clientDataJSON),
        signature: arrayBufferToBase64(assertion.response.signature),
        userHandle: arrayBufferToBase64(assertion.response.userHandle),
      },
      type: assertion.type,
    }),
  });
}

Benefits of WebAuthn

Phishing-resistant: Public key cryptography, không gửi secret qua network
No password: Không lo password leaks/breaches
Hardware-backed: Private key không rời khỏi device
Standards-based: W3C standard, support bởi all major browsers
Better UX: Touch fingerprint hoặc tap YubiKey


Single Sign-On (SSO)

SSO cho phép user login một lần và access nhiều apps.

SSO Architecture

┌─────────────────────────────────────────────────────────┐
│                                                         │
│   User logs in once                                     │
│        ↓                                                │
│   ┌────────────────┐                                    │
│   │ Identity       │                                    │
│   │ Provider (IdP) │                                    │
│   │ (Okta, Auth0)  │                                    │
│   └────────┬───────┘                                    │
│            │                                            │
│   ┌────────┼─────────────────────────┐                 │
│   │        │                         │                 │
│   ▼        ▼                         ▼                 │
│ ┌─────┐ ┌──────┐                 ┌──────┐              │
│ │ App │ │ App  │   ...           │ App  │              │
│ │  A  │ │  B   │                 │  Z   │              │
│ └─────┘ └──────┘                 └──────┘              │
│  (All apps trust IdP)                                   │
└─────────────────────────────────────────────────────────┘

SSO Protocols

1. SAML 2.0 (Security Assertion Markup Language)

Use case: Enterprise SSO (legacy, XML-based)

Flow diagram:

Browser              Service Provider (SP)      Identity Provider (IdP)
   │                  (Your App)                (Okta)
   │                       │                         │
   │ 1. Visit app          │                         │
   ├──────────────────────►│                         │
   │                       │ 2. Not authenticated    │
   │                       │    Redirect to IdP      │
   │ 3. SAMLRequest        │    with SAMLRequest     │
   ├───────────────────────┴────────────────────────►│
   │                                                  │
   │                                  4. User logs in (if needed)
   │                                     & approves   │
   │                                                  │
   │ 5. SAMLResponse (signed XML assertion)          │
   │◄─────────────────────────────────────────────────┤
   │                       │                         │
   │ 6. POST SAMLResponse  │                         │
   ├──────────────────────►│                         │
   │                       │ 7. Verify signature     │
   │                       │    Extract user info    │
   │                       │    Create session       │
   │ 8. App access         │                         │
   │◄──────────────────────┤                         │

SAML Assertion Example (simplified):

<saml:Assertion>
  <saml:Subject>
    <saml:NameID>user@example.com</saml:NameID>
  </saml:Subject>
  <saml:AttributeStatement>
    <saml:Attribute Name="email">
      <saml:AttributeValue>user@example.com</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute Name="groups">
      <saml:AttributeValue>admins</saml:AttributeValue>
    </saml:Attribute>
  </saml:AttributeStatement>
  <Signature>...</Signature>
</saml:Assertion>

2. OIDC (OpenID Connect)

Use case: Modern SSO (JSON-based, built on OAuth 2.0)

OIDC đã cover ở oauth2-and-oidc.md.
→ For SSO, IdP (Google, Okta) acts as OIDC provider.

Comparison:

SAML OIDC
Format XML JSON
Transport HTTP POST/Redirect HTTP + REST
Mobile support ❌ Poor ✅ Great
Modern ❌ Legacy ✅ Modern
Use case Enterprise Consumer + Enterprise

Recommendation: Dùng OIDC cho new projects, SAML nếu bắt buộc (enterprise requirements).


Zero Trust Architecture

Old model (Perimeter security):

┌─────────────────────────────────────┐
│  Firewall                           │
│  ┌───────────────────────────────┐  │
│  │  Internal Network (TRUSTED)   │  │
│  │  • No auth between services   │  │
│  │  • Assume all traffic is safe│  │
│  └───────────────────────────────┘  │
│                                     │
│  Outside = Dangerous                │
│  Inside = Safe                      │
└─────────────────────────────────────┘

Problem: Nếu attacker vào được network → full access.

Zero Trust model:

┌─────────────────────────────────────┐
│  NEVER TRUST, ALWAYS VERIFY         │
└─────────────────────────────────────┘

          Every request is authenticated
          & authorized, regardless of source

┌─────────┐         ┌─────────┐         ┌─────────┐
│ Service │  mTLS   │ Service │  mTLS   │ Service │
│    A    ├────────►│    B    ├────────►│    C    │
└─────────┘         └─────────┘         └─────────┘
    ↓                   ↓                   ↓
  Verify           Verify             Verify
  identity         identity           identity

Zero Trust Principles

  1. Verify explicitly: Authenticate & authorize based on all data points (identity, device, location, etc.)
  2. Least privilege access: Just-In-Time & Just-Enough-Access (JIT/JEA)
  3. Assume breach: Minimize blast radius, segment access, verify end-to-end encryption

Implementing Zero Trust

1. Mutual TLS (mTLS)

Both client AND server verify each other's certificates.

Normal TLS:
    Client verifies server certificate
    ✅ Server is who it claims to be
    ❌ Server doesn't know WHO the client is

mTLS:
    Client verifies server certificate
    Server verifies client certificate
    ✅ Both parties authenticated

Implementation (Go):

// Server with mTLS
func startMTLSServer() {
    // Load server certificate
    cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")

    // Load CA certificate (to verify clients)
    caCert, _ := os.ReadFile("ca.crt")
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)

    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        ClientAuth:   tls.RequireAndVerifyClientCert,
        ClientCAs:    caCertPool,
    }

    server := &http.Server{
        Addr:      ":8443",
        TLSConfig: tlsConfig,
    }

    server.ListenAndServeTLS("", "")
}

// Client with mTLS
func callWithMTLS(url string) {
    // Load client certificate
    cert, _ := tls.LoadX509KeyPair("client.crt", "client.key")

    // Load CA certificate (to verify server)
    caCert, _ := os.ReadFile("ca.crt")
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)

    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      caCertPool,
    }

    client := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: tlsConfig,
        },
    }

    resp, _ := client.Get(url)
    // ...
}

2. Service Mesh (Istio, Linkerd)

Service mesh automatically injects mTLS, authentication, authorization.

# Istio: Require mTLS for all services
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: default
spec:
  mtls:
    mode: STRICT

---
# Authorization policy: only service-a can call service-b
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: service-b-authz
  namespace: default
spec:
  selector:
    matchLabels:
      app: service-b
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/default/sa/service-a"]
    to:
    - operation:
        methods: ["GET", "POST"]

3. Identity-based Access (not IP-based)

// ❌ OLD: IP whitelist
if req.RemoteAddr == "10.0.1.5" {
    // Allow
}

// ✅ ZERO TRUST: Verify identity
claims := verifyJWT(req.Header.Get("Authorization"))
if claims.Service == "service-a" && claims.Role == "admin" {
    // Allow
}

4. Device Trust

Check device health before granting access:

- Is device managed by company?
- Is disk encrypted?
- Is OS up to date?
- Is antivirus running?

Example (BeyondCorp by Google):

  • User device must be registered & healthy
  • Verify identity (OAuth/OIDC)
  • Check context (location, time)
  • Grant access based on least privilege

Comparing MFA, SSO, Zero Trust

Feature Traditional MFA SSO hZero Trust
Auth method Password Password + 2nd factor One login for all apps Continuous verification
Security ⚠️ Weak ✅ Strong ⚠️ Depends on IdP ✅ Strongest
UX Simple Extra step Best (1 login) Transparent
Use case Low security apps Critical apps Enterprise High security orgs

Real-world combo: SSO + MFA + Zero Trust
→ Login once with MFA, then every service call is verified (Zero Trust).


Best Practices

MFA

✅ Prefer authenticator apps (TOTP) over SMS (SMS can be intercepted)
✅ Support hardware keys (YubiKey) for high-value accounts
✅ Provide backup codes for account recovery
✅ Enforce MFA for admins and sensitive operations

SSO

✅ Use OIDC for new systems (SAML if required by enterprise)
✅ Short-lived ID tokens (15-60 min)
✅ Validate ID token signatures
✅ Implement logout (single logout across all apps)

Zero Trust

✅ Never trust, always verify
✅ Use mTLS for service-to-service
✅ Implement least privilege (RBAC/ABAC)
✅ Monitor & log all access attempts
✅ Segment network (micro-segmentation)


Tóm tắt

Method What When How
MFA Multiple factors to prove identity Login to critical apps TOTP, WebAuthn, SMS
SSO Login once, access all apps Enterprise with many apps SAML, OIDC
Zero Trust Verify every request High-security environments mTLS, identity-based access
Passwordless No password needed Modern apps WebAuthn, passkeys

Golden rule: Defense in depth — combine multiple layers (SSO + MFA + Zero Trust) for maximum security.


Bước tiếp theo

  • ../crypto-basics.md — How TOTP, signatures work under the hood
  • ../distributed-systems-security.md — mTLS, service mesh security
  • session-vs-jwt.md — Token management for SSO