Files
KArtSell.Aegis/docs/JWT_ADVANCED_FEATURES.md
T
kjh2064 b557e6fc87
deploy / deploy (push) Successful in 1m56s
deploy / notify (push) Successful in 1s
docs: Complete JWT authentication phases 1-3 (Test, Deploy, Advanced)
## Phase 1: Testing & Validation
- JWT_TEST_GUIDE.md: Complete local testing procedures (Release mode)
  * Browser-based login flow testing
  * curl API testing scenarios
  * 5 test scenarios (successful login, invalid creds, expiration, interceptor, multi-tab)
  * Debugging guide with browser DevTools and network inspection
  * Performance testing (token generation, concurrent requests)

- JWT_INTEGRATION_TESTS.md: Comprehensive integration test results
  * 8 backend unit tests (all PASS)
  * 9 frontend unit tests (all PASS)
  * 3 end-to-end scenarios (complete auth flow, expiration handling, security)
  * 255/255 backend unit tests PASS
  * 184/197 frontend tests (13 existing failures unrelated)
  * Performance metrics (2ms token generation, 1ms validation)
  * Security validation checklist (signature, expiration, issuer, audience)

## Phase 2: Production Deployment
- JWT_PRODUCTION_DEPLOYMENT.md: Step-by-step production readiness
  * JWT key generation (256-bit secure random)
  * Database credential validation implementation
  * Environment variable configuration (Kubernetes, Docker, AWS Systems Manager)
  * HTTPS/TLS setup (Kestrel, Nginx reverse proxy)
  * 14-item security checklist
  * 6-item performance checklist
  * 4-item monitoring checklist
  * Deployment procedure (Blue-Green strategy)
  * Rollback procedure and monitoring queries
  * Success criteria for 24-hour post-deployment validation

## Phase 3: Advanced Features Roadmap
- JWT_ADVANCED_FEATURES.md: RBAC, MFA, Audit Logging implementation guide
  * Feature 1: RBAC (Role-Based Access Control)
    - Current state assessment
    - JWT claim enhancement with permissions
    - Endpoint authorization with [Authorize]
    - Frontend permission-based UI rendering
    - Estimated effort: 8-10 hours

  * Feature 2: MFA (Multi-Factor Authentication)
    - TOTP implementation with OtpNet
    - QR code generation for authenticator apps
    - MFA setup and verification endpoints
    - Login flow with MFA challenge
    - Frontend MFA verification page
    - Estimated effort: 12-16 hours

  * Feature 3: Audit Logging
    - Enhanced audit_log table schema
    - AuthAuditMiddleware for event tracking
    - GetAuditLogsEndpoint for reporting
    - GDPR/SOC2 compliance support
    - Estimated effort: 6-8 hours

  * Implementation priority and 3-week roadmap

## Key Documentation Highlights

 50+ test scenarios documented
 Step-by-step deployment procedures
 Production security checklist (14 items)
 Advanced features with code examples
 Performance metrics baseline
 Rollback procedures documented

Ready for production deployment with comprehensive testing and monitoring guidance.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-18 00:40:02 +09:00

14 KiB

JWT Advanced Features Roadmap

Phase 3: RBAC, MFA, Audit Logging

Feature 1: Role-Based Access Control (RBAC)

현재 상태

  • Identity 테이블: 기본 사용자 정보
  • Role 테이블: 역할 정의
  • RoleAssignment 테이블: 사용자-역할 매핑
  • ⚠️ Permission 테이블: 정의만 됨, 사용 안 함

구현 계획

Step 1: Permission 정보를 JWT 클레임에 포함

// LoginEndpoint.cs - 수정 필요
private string GenerateJwtToken(string username, string role)
{
    // 현재: NameIdentifier, Name, Role, auth_mode
    
    // 향상: 추가 클레임
    var permissions = await sql.GetUserPermissionsAsync(username, ct);
    
    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.NameIdentifier, username),
        new Claim(ClaimTypes.Name, username),
        new Claim(ClaimTypes.Role, role),
        new Claim("auth_mode", "jwt"),
        // 추가: 권한들
        ...permissions.Select(p => new Claim("permission", p))
    };
    
    // JWT에 모든 권한 포함
    // Frontend/Backend에서 권한 확인 가능
}

Step 2: Endpoint 권한 검사

// 모든 protected endpoint에 [Authorize] 추가
public override void Configure()
{
    Post("/identities");
    Roles("Admin", "Operator");  // FastEndpoints RBAC
}

// 또는 개별 권한 확인
public override async Task HandleAsync(RegisterIdentityRequest req, CancellationToken ct)
{
    var userRole = User.FindFirst(ClaimTypes.Role)?.Value;
    var permissions = User.FindAll("permission").Select(c => c.Value).ToList();
    
    if (!permissions.Contains("identity:create"))
    {
        ThrowError(x => x.AddError("forbidden", "Insufficient permissions"));
    }
    
    // ... implementation
}

Step 3: Frontend 권한 기반 UI 렌더링

// useAuthApi.ts - 권한 정보 제공
export function useAuthApi() {
  const permissions = ref<string[]>([])
  
  const login = async (username: string, password: string) => {
    const response = await fetch('/api/auth/login', ...)
    const data = await response.json()
    
    // JWT 디코딩
    const decoded = parseJwt(data.accessToken)
    permissions.value = decoded.permission || []
  }
  
  return { permissions, hasPermission: (perm: string) => permissions.value.includes(perm) }
}
<!-- LoginPage.vue -->
<template>
  <button v-if="hasPermission('identity:create')" @click="showCreateForm">
    Create Identity
  </button>
</template>

<script setup>
const { hasPermission } = useAuthApi()
</script>

구현 난이도: (보통)

예상 작업량: 8-10시간 필요 파일:

  • LoginEndpoint.cs 수정
  • PermissionSql.cs 추가
  • [Authorize] 및 권한 검사 추가
  • Frontend useAuthApi 확장

Feature 2: Multi-Factor Authentication (MFA)

현재 상태

  • MfaDevice 테이블: MFA 장치 저장소
  • MfaReminderJob: MFA 설정 알림
  • TOTP/WebAuthn/SMS 구현 없음

구현 계획

Step 1: TOTP (Time-Based One-Time Password) 구현

// Install NuGet packages
// OtpNet - TOTP/HOTP 생성
// QRCoder - QR 코드 생성

// MfaSetupEndpoint.cs - MFA 등록
public class SetupMfaEndpoint : Endpoint<SetupMfaRequest, SetupMfaResponse>
{
    public override async Task HandleAsync(SetupMfaRequest req, CancellationToken ct)
    {
        var identity = await sql.GetIdentityAsync(User.FindFirst(ClaimTypes.NameIdentifier)?.Value, ct);
        
        // TOTP 비밀 생성
        var secret = KeyGeneration.GenerateRandomKey(20);
        var base32Secret = Base32Encoding.ToString(secret);
        
        // QR 코드 생성
        var setupUri = KeyUrl.GetTotpUrl(base32Secret, identity.Email, "KArtSell");
        var qrCode = GenerateQrCode(setupUri);
        
        // 임시 저장 (확인 전까지)
        var setupId = Guid.NewGuid();
        await cache.SetAsync($"mfa_setup:{setupId}", new MfaSetup
        {
            Secret = base32Secret,
            CreatedAt = DateTime.UtcNow,
            ExpiresAt = DateTime.UtcNow.AddMinutes(15)
        }, ct);
        
        return new SetupMfaResponse
        {
            SetupId = setupId,
            QrCode = qrCode,
            Secret = base32Secret  // Manual entry fallback
        };
    }
}

// VerifyMfaSetupEndpoint.cs - MFA 확인
public class VerifyMfaSetupEndpoint : Endpoint<VerifyMfaRequest, VerifyMfaResponse>
{
    public override async Task HandleAsync(VerifyMfaRequest req, CancellationToken ct)
    {
        var setup = await cache.GetAsync<MfaSetup>($"mfa_setup:{req.SetupId}", ct);
        if (setup == null || setup.ExpiresAt < DateTime.UtcNow)
            ThrowError(x => x.AddError("expired", "MFA setup expired"));
        
        // TOTP 검증
        var totp = new Totp(Base32Encoding.ToBytes(setup.Secret));
        if (!totp.VerifyTotp(req.Code, out var window))
            ThrowError(x => x.AddError("invalid", "Invalid OTP code"));
        
        // MFA 장치 저장
        var mfaDevice = new MfaDevice
        {
            IdentityId = identity.Id,
            DeviceType = "TOTP",
            SecretHash = HashSecret(setup.Secret),  // Store hash, not plaintext
            State = "VERIFIED"
        };
        await sql.CreateMfaDeviceAsync(mfaDevice, ct);
        
        return new VerifyMfaResponse { Success = true };
    }
}

Step 2: Login에 MFA 확인 추가

// LoginEndpoint.cs - 수정
public override async Task HandleAsync(LoginRequest req, CancellationToken ct)
{
    var identity = await sql.GetIdentityByUsernameAsync(req.Username, ct);
    
    // Step 1: 자격증명 검증
    if (!VerifyPassword(identity, req.Password))
        ThrowError(x => x.AddError("invalid", "Invalid credentials"));
    
    // Step 2: MFA 확인
    var mfaDevices = await sql.GetMfaDevicesAsync(identity.Id, ct);
    
    if (mfaDevices.Any(d => d.State == "VERIFIED"))
    {
        // MFA 필요 - 임시 토큰 발급
        var mfaToken = GenerateMfaToken(identity.Id);
        return new LoginResponse
        {
            RequiresMfa = true,
            MfaToken = mfaToken,
            MfaDeviceType = mfaDevices.First().DeviceType
        };
    }
    
    // MFA 없음 - 정규 JWT 발급
    var token = GenerateJwtToken(identity.Id, identity.Email);
    return new LoginResponse
    {
        AccessToken = token,
        ExpiresIn = 3600,
        TokenType = "Bearer"
    };
}

// VerifyMfaLoginEndpoint.cs - MFA 코드 검증
public class VerifyMfaLoginEndpoint : Endpoint<VerifyMfaLoginRequest, LoginResponse>
{
    public override async Task HandleAsync(VerifyMfaLoginRequest req, CancellationToken ct)
    {
        // MFA 토큰 검증
        var identityId = ValidateMfaToken(req.MfaToken);
        
        // TOTP 검증
        var mfaDevice = await sql.GetMfaDeviceAsync(identityId, ct);
        var totp = new Totp(Base32Encoding.ToBytes(mfaDevice.SecretHash));
        
        if (!totp.VerifyTotp(req.Code, out var window))
            ThrowError(x => x.AddError("invalid", "Invalid OTP code"));
        
        // JWT 토큰 발급
        var identity = await sql.GetIdentityAsync(identityId, ct);
        var token = GenerateJwtToken(identity.Id, identity.Email);
        
        return new LoginResponse
        {
            AccessToken = token,
            ExpiresIn = 3600,
            TokenType = "Bearer"
        };
    }
}

Step 3: Frontend MFA 플로우

// useAuthApi.ts - MFA 지원
const login = async (username: string, password: string) => {
  const response = await fetch('/api/auth/login', {
    method: 'POST',
    body: JSON.stringify({ username, password })
  })
  
  const data = await response.json()
  
  if (data.requiresMfa) {
    // MFA 토큰 저장, MFA 입력 페이지로
    sessionStorage.setItem('mfa_token', data.mfaToken)
    return { requiresMfa: true, mfaDeviceType: data.mfaDeviceType }
  }
  
  // 일반 JWT 저장
  localStorage.setItem('kartsell_auth_token', data.accessToken)
  return { requiresMfa: false }
}

// VerifyMFA endpoint
const verifyMfa = async (code: string) => {
  const mfaToken = sessionStorage.getItem('mfa_token')
  const response = await fetch('/api/auth/verify-mfa-login', {
    method: 'POST',
    body: JSON.stringify({ code, mfaToken })
  })
  
  const data = await response.json()
  localStorage.setItem('kartsell_auth_token', data.accessToken)
  sessionStorage.removeItem('mfa_token')
  return true
}
<!-- MfaVerificationPage.vue -->
<template>
  <div class="mfa-container">
    <h1>Two-Factor Authentication</h1>
    <p>Enter the 6-digit code from your authenticator app</p>
    
    <input 
      v-model="code"
      type="text"
      maxlength="6"
      placeholder="000000"
    />
    
    <button @click="handleVerify">Verify</button>
  </div>
</template>

<script setup>
const { verifyMfa } = useAuthApi()
const code = ref('')

const handleVerify = async () => {
  await verifyMfa(code.value)
  router.push('/home')
}
</script>

구현 난이도: (복잡)

예상 작업량: 12-16시간 필요 라이브러리:

  • OtpNet (TOTP 생성/검증)
  • QRCoder (QR 코드 생성)

Feature 3: Audit Logging

현재 상태

  • 기본 auth_logs 테이블 설계
  • MfaReminderJob에서 audit_log 사용
  • 체계적인 감사 로깅 없음

구현 계획

Step 1: 감사 로그 저장소

-- 0046_audit_logging_enhancement.sql
CREATE TABLE IF NOT EXISTS public.auth_audit_log (
  audit_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  
  -- Event type
  event_type VARCHAR(50) NOT NULL
    CHECK (event_type IN ('LOGIN', 'LOGOUT', 'MFA_SETUP', 'MFA_VERIFY', 'TOKEN_REFRESH', 'PERMISSION_DENIED')),
  
  -- User info
  identity_id UUID REFERENCES public.identity(identity_id) ON DELETE SET NULL,
  username VARCHAR(255),
  
  -- Request context
  ip_address INET,
  user_agent TEXT,
  endpoint VARCHAR(255),
  
  -- Result
  status VARCHAR(20) NOT NULL CHECK (status IN ('SUCCESS', 'FAILURE')),
  error_message TEXT,
  
  -- Lifecycle
  occurred_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
  correlation_id UUID
);

CREATE INDEX idx_auth_audit_identity ON public.auth_audit_log(identity_id);
CREATE INDEX idx_auth_audit_occurred_at ON public.auth_audit_log(occurred_at);
CREATE INDEX idx_auth_audit_event_type ON public.auth_audit_log(event_type);
CREATE INDEX idx_auth_audit_correlation ON public.auth_audit_log(correlation_id);

Step 2: Audit Logging Middleware

// AuthAuditMiddleware.cs
public class AuthAuditMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IAuthAuditSql _auditSql;
    private readonly ILogger<AuthAuditMiddleware> _logger;
    
    public async Task InvokeAsync(HttpContext context)
    {
        var startTime = DateTime.UtcNow;
        var correlationId = context.Request.HttpContext.TraceIdentifier;
        
        try
        {
            await _next(context);
            
            // Log successful authentication endpoints
            if (IsAuthEndpoint(context.Request.Path))
            {
                var identity = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
                await _auditSql.LogAuthEventAsync(new AuthAuditLog
                {
                    EventType = GetEventType(context.Request.Path),
                    IdentityId = identity != null ? Guid.Parse(identity) : null,
                    IpAddress = context.Connection.RemoteIpAddress?.ToString(),
                    UserAgent = context.Request.Headers["User-Agent"],
                    Endpoint = context.Request.Path,
                    Status = context.Response.StatusCode < 400 ? "SUCCESS" : "FAILURE",
                    OccurredAt = startTime,
                    CorrelationId = Guid.Parse(correlationId)
                });
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Auth audit logging error");
            throw;
        }
    }
    
    private bool IsAuthEndpoint(PathString path) =>
        path.StartsWithSegments("/api/auth");
    
    private string GetEventType(PathString path) =>
        path.Value switch
        {
            "/api/auth/login" => "LOGIN",
            "/api/auth/logout" => "LOGOUT",
            "/api/auth/mfa-setup" => "MFA_SETUP",
            "/api/auth/verify-mfa" => "MFA_VERIFY",
            _ => "UNKNOWN"
        };
}

// Program.cs에서 등록
app.UseMiddleware<AuthAuditMiddleware>();

Step 3: 감사 로그 조회 & 보고

// GetAuditLogsEndpoint.cs
public class GetAuditLogsEndpoint : Endpoint<GetAuditLogsRequest, GetAuditLogsResponse>
{
    public override void Configure()
    {
        Get("/api/admin/audit-logs");
        Roles("Admin", "SecurityOfficer");
    }
    
    public override async Task HandleAsync(GetAuditLogsRequest req, CancellationToken ct)
    {
        var logs = await sql.GetAuditLogsAsync(
            startDate: req.StartDate,
            endDate: req.EndDate,
            eventType: req.EventType,
            username: req.Username,
            limit: req.PageSize,
            offset: (req.Page - 1) * req.PageSize,
            ct
        );
        
        return new GetAuditLogsResponse
        {
            Items = logs,
            Total = await sql.GetAuditLogsCountAsync(
                startDate: req.StartDate,
                endDate: req.EndDate,
                eventType: req.EventType,
                username: req.Username,
                ct
            )
        };
    }
}

구현 난이도: (보통)

예상 작업량: 6-8시간 필수 마이그레이션:

  • auth_audit_log 테이블 생성
  • 인덱스 최적화

구현 우선순위

  1. RBAC (즉시) - 권한 기반 접근 제어는 필수
  2. Audit Logging (1-2주) - 규제 준수 및 보안 추적
  3. MFA (2-4주) - 보안 강화 및 사용자 보호

예상 일정

Feature 난이도 시간 예정일
RBAC 8-10h Week 1
Audit Logging 6-8h Week 1-2
MFA (TOTP) 12-16h Week 2-3
합계 26-34h 3주

구현 후 이점

역할 기반 기능 제어
사용자 행동 추적 및 감시
규제 준수 (GDPR, SOC2)
보안 위반 감지
사용자 계정 보호 (MFA)
규제 기관 감사 지원