# JWT Production Deployment Guide

## Phase 2: 프로덕션 배포 준비

### 배포 전 필수 작업

#### 1️⃣ JWT 키 생성 (암호화 안전)

```powershell
# 256비트 (32바이트) 안전한 랜덤 키 생성
# Option 1: PowerShell
$bytes = New-Object Byte[] 32
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
$key = [Convert]::ToBase64String($bytes)
Write-Host "JWT_KEY=$key"

# Option 2: OpenSSL (WSL/Linux)
openssl rand -hex 32
# Output: 7e3f8c9a2b1d4e6f8a3c5b7d9e1f3a5c (convert to base64 if needed)

# Option 3: .NET CLI
dotnet user-secrets generate
```

**결과 예시:**
```
JWT_KEY=H4sIABST2GYC/0N+JxAkLxI9XxD8kWI5E9fC3x5mJ7dP8=
```

#### 2️⃣ 데이터베이스 자격증명 검증 구현

**현재 상태**: 임시 테스트 구현 (모든 username/password 수용)

**개선 사항**: Database 기반 검증

##### Step 1: 마이그레이션 생성 (Credential 테이블)

```sql
-- Migration: 0045_identity_credentials.sql
BEGIN;

CREATE TABLE IF NOT EXISTS public.identity_credential (
  credential_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  
  -- Reference to identity
  identity_id UUID NOT NULL UNIQUE REFERENCES public.identity(identity_id) ON DELETE CASCADE,
  
  -- Password storage (bcrypt hash)
  password_hash VARCHAR(255) NOT NULL,
  
  -- Credential state
  state VARCHAR(50) NOT NULL DEFAULT 'ACTIVE'
    CHECK (state IN ('ACTIVE', 'SUSPENDED', 'EXPIRED', 'REVOKED')),
  
  -- Failed login tracking
  failed_attempts INT DEFAULT 0,
  locked_until TIMESTAMP WITH TIME ZONE,
  
  -- Lifecycle
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
  
  -- Idempotency
  correlation_id UUID UNIQUE
);

CREATE INDEX idx_identity_credential_identity ON public.identity_credential(identity_id);
CREATE INDEX idx_identity_credential_state ON public.identity_credential(state);

COMMIT;
```

##### Step 2: LoginEndpoint 수정

```csharp
public override async Task HandleAsync(LoginRequest req, CancellationToken ct)
{
  logger.LogInformation("Login attempt for user: {User}", req.Username);

  if (string.IsNullOrWhiteSpace(req.Username) || string.IsNullOrWhiteSpace(req.Password))
  {
    logger.LogWarning("Login failed: missing credentials");
    ThrowError(x => x.AddError("credentials", "Username and password required"));
  }

  // FUTURE: Query database for identity by username
  // var identity = await sql.GetIdentityByUsernameAsync(req.Username, ct);
  
  // FUTURE: Get credential record
  // var credential = await sql.GetCredentialAsync(identity.Id, ct);
  
  // FUTURE: Verify password
  // if (!BCrypt.Net.BCrypt.Verify(req.Password, credential.PasswordHash))
  // {
  //   await sql.RecordFailedLoginAttemptAsync(credential.Id, ct);
  //   ThrowError(x => x.AddError("credentials", "Invalid credentials"));
  // }
  
  // TEMPORARY: Accept any non-empty credentials
  var token = GenerateJwtToken(req.Username, req.Role ?? "User");
  logger.LogInformation("Token issued for user: {User}", req.Username);

  // ... rest of implementation
}
```

#### 3️⃣ 환경 변수 설정 (배포 시)

**Kubernetes Secret:**
```yaml
apiVersion: v1
kind: Secret
metadata:
  name: kartsell-jwt
type: Opaque
data:
  JWT_KEY: SGg0c0lBQlNUM...  # Base64 encoded
```

**Docker/.env:**
```bash
JWT_KEY=H4sIABST2GYC/0N+JxAkLxI9XxD8kWI5E9fC3x5mJ7dP8=
KARTSELL_POSTGRES=Host=db.production.internal;Port=5432;Database=kartselldb;Username=kartsell;Password=...
```

**AWS Systems Manager:**
```bash
aws ssm put-parameter \
  --name /kartsell/jwt/key \
  --value "H4sIABST2GYC/0N+JxAkLxI9XxD8kWI5E9fC3x5mJ7dP8=" \
  --type "SecureString"
```

#### 4️⃣ appsettings 배포 설정

**appsettings.Release.json 검증:**
```json
{
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://0.0.0.0:5002"
      }
    }
  },
  "Authentication": {
    "Mode": "JWT"
  },
  "Jwt": {
    "Key": "${JWT_KEY}",  // ✅ Environment variable
    "Issuer": "KArtSell.Aegis",
    "Audience": "KArtSell.Aegis",
    "ExpirationMinutes": 60
  },
  "ConnectionStrings": {
    "Postgres": "${KARTSELL_POSTGRES}"  // ✅ Environment variable
  },
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information"
    }
  }
}
```

#### 5️⃣ HTTPS/TLS 설정

**Kestrel HTTPS:**
```json
{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://0.0.0.0:443",
        "Certificate": {
          "Path": "/etc/ssl/certs/kartsell.pfx",
          "Password": "${CERT_PASSWORD}"
        }
      }
    }
  }
}
```

**Nginx Reverse Proxy:**
```nginx
upstream backend {
    server kartsell-host:5002;
}

server {
    listen 443 ssl http2;
    server_name api.kartsell.taxbaik.com;
    
    ssl_certificate /etc/nginx/ssl/kartsell.crt;
    ssl_certificate_key /etc/nginx/ssl/kartsell.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    
    location /api/auth/login {
        proxy_pass http://backend;
        proxy_set_header Authorization "";  # Don't forward client auth
    }
    
    location /api {
        proxy_pass http://backend;
        proxy_set_header Authorization $http_authorization;
        proxy_pass_header Authorization;
    }
}
```

## 배포 체크리스트

### 보안

- [ ] JWT_KEY 환경변수 설정 (256+ bits, cryptographically secure)
- [ ] HTTPS only (HTTP → HTTPS redirect)
- [ ] TLS 1.2+ enforced
- [ ] HSTS header enabled (Strict-Transport-Security)
- [ ] CORS properly configured (whitelist specific origins)
- [ ] Rate limiting on /api/auth/login (max 5 attempts/min per IP)
- [ ] Database credentials in secret manager (not hardcoded)
- [ ] JWT key rotation schedule planned (annual minimum)

### 성능

- [ ] Connection pooling configured (min 10, max 50)
- [ ] Caching enabled for authentication checks
- [ ] Load balancer session affinity configured
- [ ] CDN configured for static assets
- [ ] Database query optimization verified

### 모니터링

- [ ] Authentication success/failure metrics logged
- [ ] Failed login attempts alerting (>10/min = alert)
- [ ] JWT validation errors tracked
- [ ] Token expiration events logged
- [ ] Authorization failures monitored

### 데이터베이스

- [ ] Backup schedule configured (daily minimum)
- [ ] Password hashing algorithm decided (bcrypt/argon2)
- [ ] Credential table indexed for fast lookups
- [ ] Audit logging enabled
- [ ] Database connection encryption (SSL)

### 배포

- [ ] Database migrations pre-validated
- [ ] Rollback plan documented
- [ ] Canary deployment configured (5% → 25% → 100%)
- [ ] Health checks configured (/health/ready endpoint)
- [ ] Log aggregation configured (ELK/Datadog)

## 배포 절차

### 단계 1: 프로덕션 환경 준비

```bash
# 1. 환경 변수 설정
export JWT_KEY="H4sIABST2GYC/0N+JxAkLxI9XxD8kWI5E9fC3x5mJ7dP8="
export KARTSELL_POSTGRES="Host=prod-db;Port=5432;Database=kartselldb;Username=kartsell;Password=..."

# 2. 데이터베이스 마이그레이션 실행
dotnet KArtSell.DbMigrator.dll

# 3. 헬스 체크
curl https://api.kartsell.taxbaik.com/health/ready
# Expected: 200 OK
```

### 단계 2: 배포 (Blue-Green)

```bash
# Blue: 현재 운영 환경 (v1.0)
# Green: 새 배포 환경 (v2.0)

# 1. Green 환경에 v2.0 배포
docker run -d \
  -e JWT_KEY=$JWT_KEY \
  -e KARTSELL_POSTGRES=$KARTSELL_POSTGRES \
  -p 5002:5002 \
  kartsell:v2.0

# 2. Green 환경 헬스 체크
curl http://localhost:5002/health/ready

# 3. Green 환경 테스트
# - Login flow
# - API requests with JWT
# - Token expiration

# 4. 로드 밸런서 Green으로 전환
# Blue → Green traffic switch

# 5. Blue 환경 모니터링 (rollback 준비)
# 30분 동안 이상 없으면 Blue 종료
```

### 단계 3: 배포 후 검증

```bash
# 1. JWT 토큰 발급 테스트
curl -X POST https://api.kartsell.taxbaik.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"test","password":"test","role":"Admin"}'

# Expected response:
# {
#   "accessToken": "eyJhbGc...",
#   "expiresIn": 3600,
#   "tokenType": "Bearer"
# }

# 2. API 엔드포인트 인증 테스트
TOKEN="eyJhbGc..."
curl https://api.kartsell.taxbaik.com/api/identities \
  -H "Authorization: Bearer $TOKEN"

# Expected: 200 OK (또는 관련 비즈니스 응답)

# 3. 모니터링 대시보드 확인
# - Authentication success rate
# - API latency
# - Error rates

# 4. 로그 확인
# - 비정상적인 인증 실패 없음
# - 토큰 검증 오류 없음
```

## 롤백 절차

토큰 생성/검증 오류 발생 시:

```bash
# 1. 즉시 Blue 환경으로 복구
# 로드 밸런서 Blue로 전환

# 2. 문제 분석
# - JWT_KEY 환경변수 확인
# - 데이터베이스 연결 확인
# - 로그 분석

# 3. 문제 수정 후 재배포
```

## 모니터링 쿼리

### 인증 성공률

```sql
SELECT 
  DATE_TRUNC('hour', created_at) as hour,
  COUNT(*) FILTER (WHERE status = 'success') as success_count,
  COUNT(*) FILTER (WHERE status = 'failure') as failure_count,
  ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'success') / COUNT(*), 2) as success_rate
FROM auth_logs
WHERE created_at > NOW() - INTERVAL '24 hours'
GROUP BY DATE_TRUNC('hour', created_at)
ORDER BY hour DESC;
```

### 토큰 검증 오류

```sql
SELECT error_message, COUNT(*) as count
FROM jwt_validation_errors
WHERE created_at > NOW() - INTERVAL '1 hour'
GROUP BY error_message
ORDER BY count DESC;
```

## 성공 기준

배포 후 최소 24시간 모니터링:

- [ ] Authentication success rate > 99%
- [ ] API latency < 200ms (p95)
- [ ] Token validation errors = 0
- [ ] Failed login attempts < 5/minute average
- [ ] No database connection errors
- [ ] User reports = 0

**이 모든 기준을 충족하면 배포 완료! ✅**
