🔑 OAuth 2.0

OAuth 2.0은 API 액세스를 위해 가장 널리 사용되는 인증 프레임워크입니다. 적절한 권한 부여 유형을 선택하는 것이 중요합니다.

보조금 유형 선택

보조금 유형 사용 사례 보안 수준
Authorization Code + PKCE SPA 및 모바일 앱(권장) 최고
Authorization Code 서버 측 웹 앱 높음
Client Credentials 서버 간 통신(M2M) Medium
암시적(사용 중단됨) 레거시 SPA 낮음
리소스 소유자 비밀번호(사용 중단됨) 신뢰도가 높은 퍼스트 파티 전용 낮음
암시적 부여 사용 안 함

토큰이 URL 조각에 노출되기 때문에 암시적 부여는 이제 더 이상 사용되지 않습니다. SPA에는 인증 코드 + PKCE를 사용합니다.

인증 코드 + PKCE 흐름

JavaScript PKCE 챌린지 생성
// 1. 코드 검증기를 무작위로 생성합니다.
function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return btoa(String.fromCharCode(...array))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

// 2. SHA-256으로 code_challenge를 생성합니다.
async function generateCodeChallenge(verifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const digest = await crypto.subtle.digest('SHA-256', data);
  return btoa(String.fromCharCode(...new Uint8Array(digest)))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

// 3. 권한 요청
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier);

const authUrl = `https://auth.example.com/authorize?` +
  `response_type=code&` +
  `client_id=${clientId}&` +
  `redirect_uri=${redirectUri}&` +
  `code_challenge=${challenge}&` +
  `code_challenge_method=S256&` +
  `scope=read write`;

🎫 JWT(JSON 웹 토큰)

JWT는 상태 비저장 인증 토큰으로 널리 사용되지만 구현 실수는 보안 취약점으로 직결됩니다.

JWT 보안 고려 사항

✅ Do

  • 알고리즘 명시적 지정(HS256/RS256)
  • 짧은 만료 시간 설정(15분 이하)
  • 발급자(iss) 및 대상(aud) 유효성 검사
  • 충분한 길이(256비트 이상)의 비밀 키를 생성합니다.
  • 새로 고침 토큰에 대한 로테이션 구현

❌ 하지 않음

  • 민감한 정보를 JWT 페이로드에 저장(Base64는 암호화되지 않음)
  • Allow alg: "none"
  • 로컬스토리지에 토큰 저장(XSS에 취약함)
  • 토큰 해지 관리 소홀
  • RS256 공개 키를 HS256 비밀 키로 사용

안전한 JWT 구현

JavaScript (Node.js) 토큰 발급 및 검증
const jwt = require('jsonwebtoken');

// --- 토큰 발행 ---
function issueTokens(user) {
  const accessToken = jwt.sign(
    { sub: user.id, role: user.role },
    process.env.JWT_SECRET,
    {
      algorithm: 'HS256',
      expiresIn: '15m',
      issuer: 'api.example.com',
      audience: 'app.example.com',
    }
  );

  const refreshToken = jwt.sign(
    { sub: user.id, type: 'refresh' },
    process.env.REFRESH_SECRET,
    { algorithm: 'HS256', expiresIn: '7d' }
  );

  return { accessToken, refreshToken };
}

// --- 토큰 확인 ---
function verifyAccessToken(token) {
  return jwt.verify(token, process.env.JWT_SECRET, {
    algorithms: ['HS256'],   // ← 항상 지정하세요!
    issuer: 'api.example.com',
    audience: 'app.example.com',
  });
}

토큰 저장 위치 비교

저장 위치 XSS 저항 CSRF 저항 권장 사항
HttpOnly Cookie △ (SameSite 설정 필요) 추천
메모리(가변) 페이지 탐색 중 분실
localStorage ✕ (XSS를 통해 도용될 수 있음) 권장하지 않음
sessionStorage ✕ (XSS를 통해 도용될 수 있음) 제한된 사용
권장: HttpOnly + 보안 + 동일 사이트 쿠키

액세스 토큰을 쿠키에 HttpOnly, Secure 및 SameSite=Strict 속성을 사용하여 저장하는 것이 가장 안전한 방법입니다. 이렇게 하면 XSS 공격을 통한 토큰 도용을 방지할 수 있습니다.

🗝 API 키 관리

API 키는 간단한 인증 메커니즘이지만 부적절하게 관리하면 심각한 위험을 초래할 수 있습니다.

API 주요 모범 사례

생성 및 저장

충분한 엔트로피(256비트 이상)로 생성합니다. 데이터베이스에 일반 텍스트를 보관하지 말고 해시된 상태로 저장합니다.

범위 제한

키당 최소 범위(읽기 전용, 특정 리소스 전용 등)를 할당합니다. 와일드카드 권한은 피하세요.

회전

키를 정기적으로 교체합니다(90일 이내). 오래된 키에 대한 유예 기간을 설정합니다. 유출 시 즉시 해지하세요.

전송 방법

헤더(X-API-Key 또는 권한 부여)를 통해 전송합니다. URL 쿼리 매개변수에는 포함하지 마세요(로그에 표시됨).

환경 변수 관리

소스 코드에 키를 하드코딩하지 마세요. 환경 변수 또는 비밀 관리자를 사용하여 관리하세요.

모니터링 및 로깅

API 키 사용 모니터링. 비정상적인 패턴(대량 요청, 알 수 없는 IP로부터의 액세스)을 감지합니다.

API 키 유효성 검사 구현 예시

JavaScript (Express) 해시된 API 키 유효성 검사
const crypto = require('crypto');

// SHA-256으로 API 키를 해시하고 비교합니다.
async function validateApiKey(req, res, next) {
  const apiKey = req.headers['x-api-key'];
  if (!apiKey) {
    return res.status(401).json({ error: 'API key required' });
  }

  // 해시하고 DB에서 조회합니다(해시를 비교하므로 timingSafeEqual은 필요하지 않습니다).
  const hashedKey = crypto
    .createHash('sha256')
    .update(apiKey)
    .digest('hex');

  const keyRecord = await db.findApiKey(hashedKey);

  if (!keyRecord || keyRecord.revokedAt) {
    return res.status(403).json({ error: 'Invalid API key' });
  }

  // 만료 확인
  if (keyRecord.expiresAt && new Date() > keyRecord.expiresAt) {
    return res.status(403).json({ error: 'API key expired' });
  }

  req.apiClient = keyRecord;
  next();
}

🤖 Securing LLM & AI Agent API Access

AI agents and LLM-powered applications require additional authentication and authorization patterns beyond traditional API security.

Model-Specific Scopes

Assign different access levels per model tier (e.g., GPT-4 requires elevated scope). Prevent unauthorized use of expensive or sensitive models.

Token Budget Enforcement

Embed token budgets in access tokens or API key metadata. Enforce per-request and per-day limits to prevent denial-of-wallet attacks.

Prompt-Level Authorization

Check user permissions before executing certain prompt types (e.g., code generation, data analysis). Map prompt categories to role-based permissions.

Multi-Tenant Isolation

Ensure conversation history, fine-tuned models, and RAG data are isolated per tenant. Use tenant-scoped API keys with namespace enforcement.

Agent Identity & Attestation

Assign cryptographic identities to AI agents. Use signed JWTs for inter-agent communication. Verify agent identity before granting tool access.

Credential Rotation for AI Services

Automate API key rotation for LLM providers. Use short-lived tokens for agent sessions. Revoke credentials immediately when agents are decommissioned.

Agent Tool Access Authorization

Scenario Auth Method Required Scope Approval
Agent reads public data API Key data:read Automatic
Agent modifies user data OAuth 2.0 (delegated) data:write User consent required
Agent calls external API OAuth 2.0 + mTLS external:invoke Human-in-the-loop
Agent executes code / shell Scoped JWT + Sandbox exec:sandbox Admin approval + audit log

Scoped OAuth Token for AI Agents (Python)

Python Agent Token Issuance with Scope Enforcement
import jwt
import time
from datetime import datetime, timedelta

class AgentTokenIssuer:
    """Issues scoped, short-lived tokens for AI agents."""

    ALLOWED_SCOPES = {
        "reader": ["data:read"],
        "writer": ["data:read", "data:write"],
        "executor": ["data:read", "data:write", "exec:sandbox"],
    }

    def __init__(self, secret_key: str):
        self.secret_key = secret_key

    def issue_agent_token(
        self,
        agent_id: str,
        role: str,
        tenant_id: str,
        token_budget: int = 10000,
        ttl_minutes: int = 15,
    ) -> str:
        # Validate role and resolve scopes
        if role not in self.ALLOWED_SCOPES:
            raise ValueError(f"Invalid role: {role}")

        payload = {
            "sub": agent_id,
            "tenant": tenant_id,
            "scopes": self.ALLOWED_SCOPES[role],
            "token_budget": token_budget,
            "iat": datetime.utcnow(),
            "exp": datetime.utcnow() + timedelta(minutes=ttl_minutes),
            "type": "agent",
        }
        return jwt.encode(payload, self.secret_key, algorithm="HS256")

    def verify_tool_access(self, token: str, required_scope: str) -> dict:
        # Decode and verify agent token
        payload = jwt.decode(
            token, self.secret_key, algorithms=["HS256"]
        )
        if required_scope not in payload["scopes"]:
            raise PermissionError(
                f"Agent {payload['sub']} lacks scope: {required_scope}"
            )
        return payload
OWASP References

Related: LLM06: Excessive Agency, ASI01: Excessive Agency, ASI03: Insecure Tool/Function Calling

인증 방법 비교

방법 무국적자 사용 사례 보안 고려 사항
OAuth 2.0 + PKCE 사용자 승인이 필요한 API 상태/PKCE 필수, 토큰 만료 관리
JWT Bearer 마이크로서비스 간 커뮤니케이션 알고리즘 사양 필요, 페이로드에 민감한 데이터 없음
API 키 서버 간, 외부 통합 회전 및 범위 제한 필요
mTLS 높은 보안성의 M2M 통신 인증서 관리를 위한 높은 운영 비용
세션 쿠키 기존 웹 애플리케이션 CSRF 보호 필요, 확장성 문제