API 인증 방식 총정리 (API Key, OAuth, JWT)

API 보안, 왜 중요한가요?

API를 개발하다 보면 반드시 마주치는 질문이 있습니다. “누가 이 API를 호출하는지 어떻게 확인하지?” API 인증은 단순히 기술적인 문제가 아닙니다. 사용자 데이터 보호, 서비스 안정성, 법적 책임까지 연결되는 핵심 보안 요소입니다. 이번 포스팅에서는 실무에서 가장 많이 사용되는 세 가지 인증 방식인 API Key, OAuth 2.0, JWT를 완벽하게 비교 분석하고, 각각을 언제 어떻게 사용해야 하는지 알아보겠습니다.

API 인증이란 무엇인가?

API 인증은 API를 호출한 요청이 정상적인 권한이 있는 호출인지를 확인하는 과정입니다 Brunch. 쉽게 말해, 건물에 들어갈 때 출입증을 확인하는 것과 같습니다. API 인증 없이는 누구나 여러분의 서버에 접근하여 데이터를 조회하거나 수정할 수 있게 됩니다.

인증(Authentication) vs 인가(Authorization)

혼동하기 쉬운 두 개념을 먼저 정리하고 가겠습니다.

  • 인증(Authentication): “당신이 누구인지” 확인하는 과정 (예: 로그인)
  • 인가(Authorization): “당신이 무엇을 할 수 있는지” 확인하는 과정 (예: 권한 체크)

API 보안은 이 두 가지를 모두 포함합니다.

1. API Key 방식: 가장 단순하고 기본적인 인증

API Key란?

API Key는 인증에서 가장 단순한 방법으로, 권한이 있는 사용자에게만 나누어주고 모든 API 요청에 사용하도록 하는 방법입니다 Brunch. 마치 건물 출입을 위한 개인 카드키와 같습니다.

API Key의 구조

API Key는 일반적으로 랜덤하게 생성된 긴 문자열입니다.

예시: AIzaSyDhj8K9xT2nPqR5mL3bW7vC1fQ8eU6aX0Y

API Key 사용 방법

API Key는 주로 세 가지 방식으로 전달됩니다.

방법 1: Query Parameter (가장 간단)

javascript

// URL에 직접 포함
fetch('https://api.example.com/users?api_key=YOUR_API_KEY')
  .then(response => response.json())
  .then(data => console.log(data));

방법 2: HTTP Header (권장)

javascript

// Authorization 헤더 사용
fetch('https://api.example.com/users', {
  headers: {
    'X-API-Key': 'YOUR_API_KEY'
  }
})
  .then(response => response.json())
  .then(data => console.log(data));

방법 3: Request Body

javascript

// POST 요청의 body에 포함
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    api_key: 'YOUR_API_KEY',
    data: { /* 실제 데이터 */ }
  })
});

API Key의 장점

  • 구현이 매우 간단함
  • 빠른 검증 속도
  • 서버 부하가 적음
  • 디버깅이 쉬움

API Key의 단점

  • 보안성이 상대적으로 낮음
  • 한번 유출되면 위험
  • 사용자별 세밀한 권한 관리 어려움
  • 만료 기능이 없는 경우 영구적으로 유효

API Key 실전 팁

1. 환경변수로 관리하기

javascript

// ❌ 나쁜 예: 코드에 직접 하드코딩
const API_KEY = 'AIzaSyDhj8K9xT2nPqR5mL3bW7vC1fQ8eU6aX0Y';

// ✅ 좋은 예: 환경변수 사용
const API_KEY = process.env.API_KEY;

2. HTTPS 필수 사용

보안적으로 중요한 요청은 반드시 HTTPS를 사용해야 합니다. HTTPS는 요청 내용을 암호화하여 공격자가 중간에 탈취하더라도 내용을 볼 수 없게 막아줍니다 Yozm.

3. Rate Limiting 적용

javascript

// API 호출 횟수 제한 예시 (Express.js)
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15분
  max: 100 // 최대 100번 요청
});

app.use('/api/', limiter);
```

**API Key를 사용해야 하는 경우**

- 공개 API 서비스 (날씨, 지도 등)
- 서버 간 통신
- 간단한 프로토타입
- 사용자 추적이 주 목적일 때

**2. JWT (JSON Web Token): 현대적이고 확장 가능한 인증**

**JWT란?**

JWT는 서버와 클라이언트 사이에서 안전한 데이터 전송을 위해 사용하는 토큰 기반 인증 방식입니다. JWT는 서명된 JSON 포맷 토큰으로, 인증과 토큰의 무결성을 보장합니다 .

**JWT의 구조**

JWT는 점(.)을 구분자로 Header, Payload, Signature 세 부분으로 구성됩니다 .
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NSIsIm5hbWUiOiJKb2huIERvZSIsImV4cCI6MTYxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

부분 1: Header (헤더)

토큰의 타입과 해싱 알고리즘을 정의합니다.

json

{
  "alg": "HS256",
  "typ": "JWT"
}

부분 2: Payload (페이로드)

Payload에는 토큰에 담을 정보가 들어있으며, 이를 클레임(claim)이라고 부릅니다 Velopert.

json

{
  "userId": "12345",
  "name": "John Doe",
  "email": "john@example.com",
  "iat": 1616239022,
  "exp": 1616325422
}
```

**표준 클레임**

- iss (issuer): 토큰 발급자
- sub (subject): 토큰 제목
- aud (audience): 토큰 대상자
- exp (expiration): 만료 시간 (필수!)
- iat (issued at): 발급 시간
- nbf (not before): 활성화 시간

**부분 3: Signature (서명)**

Header와 Payload는 단순히 인코딩된 값이기 때문에 제3자가 복호화 및 조작할 수 있지만, Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화할 수 없습니다 .
```
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

JWT 실전 구현

JWT 생성하기 (Node.js)

javascript

const jwt = require('jsonwebtoken');

// JWT 생성 함수
function createToken(userId, email) {
  const payload = {
    userId: userId,
    email: email,
    role: 'user'
  };
  
  const secret = process.env.JWT_SECRET;
  const options = {
    expiresIn: '1h', // 1시간 후 만료
    issuer: 'myapp.com'
  };
  
  return jwt.sign(payload, secret, options);
}

// 사용 예시
const token = createToken('12345', 'user@example.com');
console.log('JWT:', token);

JWT 검증하기

javascript

function verifyToken(token) {
  try {
    const secret = process.env.JWT_SECRET;
    const decoded = jwt.verify(token, secret);
    
    console.log('사용자 ID:', decoded.userId);
    console.log('이메일:', decoded.email);
    
    return decoded;
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      throw new Error('토큰이 만료되었습니다');
    } else if (error.name === 'JsonWebTokenError') {
      throw new Error('유효하지 않은 토큰입니다');
    }
    throw error;
  }
}

JWT를 API 요청에 사용하기

javascript

// 로그인하여 JWT 받기
async function login(email, password) {
  const response = await fetch('https://api.example.com/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ email, password })
  });
  
  const data = await response.json();
  // JWT를 localStorage에 저장
  localStorage.setItem('token', data.token);
}

// JWT를 사용하여 보호된 리소스 접근
async function getUserProfile() {
  const token = localStorage.getItem('token');
  
  const response = await fetch('https://api.example.com/profile', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  
  return await response.json();
}

Refresh Token 패턴

JWT의 단점은 토큰이 탈취당하면 만료될 때까지 대처가 불가능하다는 점입니다 Brunch. 이를 해결하기 위해 Refresh Token 패턴을 사용합니다.

javascript

// Access Token(짧은 수명) + Refresh Token(긴 수명) 발급
function createTokenPair(userId) {
  const accessToken = jwt.sign(
    { userId, type: 'access' },
    process.env.ACCESS_SECRET,
    { expiresIn: '15m' } // 15분
  );
  
  const refreshToken = jwt.sign(
    { userId, type: 'refresh' },
    process.env.REFRESH_SECRET,
    { expiresIn: '7d' } // 7일
  );
  
  return { accessToken, refreshToken };
}

// Access Token 갱신
async function refreshAccessToken(refreshToken) {
  try {
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
    
    // 새로운 Access Token 발급
    const newAccessToken = jwt.sign(
      { userId: decoded.userId, type: 'access' },
      process.env.ACCESS_SECRET,
      { expiresIn: '15m' }
    );
    
    return newAccessToken;
  } catch (error) {
    throw new Error('Refresh Token이 유효하지 않습니다');
  }
}

JWT의 장점

  • 서버에 세션을 저장할 필요 없음 (Stateless)
  • 확장성이 뛰어남 (마이크로서비스에 적합)
  • 토큰 자체에 정보 포함
  • 모바일 앱에 최적화

JWT의 단점

  • Payload는 암호화되지 않음 (민감 정보 저장 금지)
  • 토큰 크기가 상대적으로 큼
  • 한번 발급되면 강제 만료 어려움

JWT를 사용해야 하는 경우

  • 현대적인 웹/모바일 애플리케이션
  • 마이크로서비스 아키텍처
  • SPA (Single Page Application)
  • Stateless 인증이 필요한 경우

3. OAuth 2.0: 제3자 인증의 표준

OAuth 2.0이란?

OAuth 2.0은 페이스북, 구글, 트위터 같은 API 서비스 제공자들이 인증을 대신 해주는 제3자 인증 방식입니다 Dongwooklee96. “구글 로그인”, “카카오 로그인”이 바로 OAuth 2.0입니다.

OAuth 2.0의 4가지 주요 역할

OAuth 2.0을 구성하는 4가지 역할은 리소스 소유자(Resource Owner), 클라이언트(Client), 권한 서버(Authorization Server), 리소스 서버(Resource Server)입니다 Ncloud-docs.

  1. Resource Owner (리소스 소유자): 실제 사용자
  2. Client (클라이언트): 우리가 개발하는 애플리케이션
  3. Authorization Server (인증 서버): 구글, 카카오 등의 인증 서버
  4. Resource Server (리소스 서버): 사용자 정보를 가진 서버

OAuth 2.0 인증 흐름 (Authorization Code Grant)

인증 코드 부여 방식은 권한 부여 승인을 위해 자체 생성한 인증 코드를 전달하는 방식으로 가장 많이 사용되는 기본 방식입니다 Ncloud-docs.

1단계: 애플리케이션 등록

먼저 구글, 카카오 등의 개발자 콘솔에서 애플리케이션을 등록합니다.

  • Client ID 발급받기
  • Client Secret 발급받기
  • Redirect URI 등록하기

2단계: 사용자를 인증 페이지로 리디렉션

javascript

// 구글 OAuth 로그인 버튼 클릭 시
function loginWithGoogle() {
  const clientId = 'YOUR_CLIENT_ID';
  const redirectUri = 'https://yourapp.com/callback';
  const scope = 'email profile';
  
  const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
    `client_id=${clientId}&` +
    `redirect_uri=${encodeURIComponent(redirectUri)}&` +
    `response_type=code&` +
    `scope=${encodeURIComponent(scope)}`;
  
  // 구글 로그인 페이지로 이동
  window.location.href = authUrl;
}
```

**3단계: 사용자 인증 및 권한 동의**

사용자는 구글 로그인 페이지에서 로그인하고, 앱이 요청한 권한을 승인합니다.

**4단계: Authorization Code 받기**

인증이 성공하면 등록한 Redirect URI로 돌아옵니다.
```
https://yourapp.com/callback?code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7

5단계: Authorization Code로 Access Token 교환

javascript

// 서버 측 코드 (Node.js + Express)
app.get('/callback', async (req, res) => {
  const code = req.query.code;
  
  try {
    // Authorization Code를 Access Token으로 교환
    const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams({
        code: code,
        client_id: process.env.GOOGLE_CLIENT_ID,
        client_secret: process.env.GOOGLE_CLIENT_SECRET,
        redirect_uri: 'https://yourapp.com/callback',
        grant_type: 'authorization_code'
      })
    });
    
    const tokens = await tokenResponse.json();
    // tokens.access_token 사용
    
    res.redirect('/dashboard');
  } catch (error) {
    console.error('OAuth 오류:', error);
    res.redirect('/login?error=auth_failed');
  }
});

6단계: Access Token으로 사용자 정보 가져오기

javascript

async function getUserInfo(accessToken) {
  const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
    headers: {
      'Authorization': `Bearer ${accessToken}`
    }
  });
  
  const userInfo = await response.json();
  console.log('사용자 정보:', userInfo);
  // { id, email, name, picture, ... }
  
  return userInfo;
}

OAuth 2.0 Scope (권한 범위)

OAuth 2.0은 스코프라는 개념을 통해서 사용자 리소스에 대한 클라이언트의 접근 범위를 제한할 수 있습니다 Hudi.

javascript

// 다양한 Scope 예시
const scopes = {
  basic: 'email profile',
  calendar: 'https://www.googleapis.com/auth/calendar',
  drive: 'https://www.googleapis.com/auth/drive.readonly'
};

OAuth 2.0의 장점

  • 사용자 비밀번호를 저장하지 않음
  • 신뢰할 수 있는 제3자 인증
  • 세밀한 권한 제어 가능
  • 사용자 경험 향상 (간편 로그인)

OAuth 2.0의 단점

  • 구현이 복잡함
  • 외부 서비스 의존성
  • 디버깅이 어려움

OAuth 2.0을 사용해야 하는 경우

  • 소셜 로그인 구현
  • 사용자 비밀번호를 저장하고 싶지 않을 때
  • 제3자 서비스 연동 (구글 캘린더, 페이스북 그래프 등)
  • B2C 서비스

세 가지 인증 방식 비교표

비교 항목API KeyJWTOAuth 2.0
구현 난이도쉬움보통어려움
보안 수준낮음높음매우 높음
확장성낮음높음높음
사용자 정보없음포함 가능3자 제공
만료 기능선택적필수필수
적합한 용도공개 API일반 웹앱소셜로그인

실전 보안 팁

1. HTTPS는 필수

모든 인증 방식에서 HTTPS는 필수입니다. HTTP로 인증 정보를 전송하면 중간자 공격에 취약합니다.

2. 민감 정보는 JWT Payload에 넣지 마세요

javascript

// ❌ 나쁜 예
const payload = {
  userId: 123,
  password: 'user_password', // 절대 금지!
  creditCard: '1234-5678-9012-3456' // 절대 금지!
};

// ✅ 좋은 예
const payload = {
  userId: 123,
  email: 'user@example.com',
  role: 'user'
};

3. 토큰 만료 시간 적절히 설정

javascript

// Access Token: 짧게 (15분 ~ 1시간)
// Refresh Token: 길게 (7일 ~ 30일)
const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });
const refreshToken = jwt.sign(payload, secret, { expiresIn: '7d' });

4. Rate Limiting 적용

과도한 API 호출을 방지하여 DDoS 공격을 막습니다.

5. API Key 로테이션

정기적으로 API Key를 교체하여 보안을 강화합니다.

어떤 인증 방식을 선택해야 할까?

API Key를 선택하세요:

  • 공개 API 서비스 (날씨, 지도)
  • 서버 간 통신
  • 프로토타입 단계
  • 간단한 사용자 추적

JWT를 선택하세요:

  • 현대적인 웹/모바일 앱
  • SPA (React, Vue, Angular)
  • 마이크로서비스
  • Stateless 아키텍처

OAuth 2.0을 선택하세요:

  • 소셜 로그인 기능
  • 사용자 비밀번호 관리 부담 감소
  • 제3자 서비스 데이터 접근
  • B2C 서비스

복합 사용도 가능합니다!

많은 서비스에서 여러 인증 방식을 함께 사용합니다.

javascript

// 예시: 일반 로그인 (JWT) + 소셜 로그인 (OAuth)
router.post('/login', jwtLogin); // JWT 사용
router.get('/login/google', oauthLogin); // OAuth 사용
router.get('/api/public', apiKeyAuth); // API Key 사용

마치며

API 인증은 애플리케이션 보안의 첫 번째 관문입니다. API Key는 간단하지만 제한적이고, JWT는 현대적이고 확장 가능하며, OAuth 2.0은 제3자 인증의 표준입니다. 각 방식의 특징을 이해하고 서비스 요구사항에 맞는 인증 방식을 선택하는 것이 중요합니다.

보안은 한 번 설정하고 끝나는 것이 아닙니다. 정기적인 보안 점검, 토큰 만료 시간 조정, 비밀키 로테이션 등 지속적인 관리가 필요합니다. 지금 바로 여러분의 프로젝트에 적합한 인증 방식을 선택하고 구현해보세요!