토큰 교환

엔드포인트

POST /v1/oauth/token

설명

Authorization Code를 Access Token과 Refresh Token으로 교환합니다. 보안을 더 강화하고 싶으시다면 PKCE (Proof Key for Code Exchange) 방식을 사용하여 code_verifier로 인증하는 것도 좋습니다.

발급되는 토큰은 다음과 같은 용도로 사용됩니다:

  • Access Token: 사용자 리소스 접근 (1시간 유효)
  • Refresh Token: Access Token 갱신 (30일 유효)

이 엔드포인트는 grant_type 파라미터로 토큰 교환과 토큰 갱신을 모두 처리합니다. Authorization Code 교환 시에는 grant_typeauthorization_code로 설정하세요.

요청 파라미터

인증 방식 안내

code_verifier를 사용하는 PKCE 방식과 client_secret 방식 중 선택하실 수 있습니다. PKCE 방식이 몇 가지 장점이 있어 가능하시다면 사용해보시는 것도 좋습니다.

모든 파라미터는 JSON 형식으로 요청 본문(body)에 포함되어야 합니다.

PKCE 방식 (권장)

파라미터타입필수 여부설명예시
grant_typeString필수요청 타입 (authorization_code 고정)authorization_code
codeString필수/v1/oauth/authorize 플로우에서 발급받은 Authorization Codeabc123def456
client_idString필수DataGSM에서 발급받은 클라이언트 IDyour-client-id
redirect_uriString필수Authorization Code 발급 시 사용한 리다이렉트 URIhttps://your-app.com/callback
code_verifierString필수PKCE Code Verifier (Authorization Code 발급 시 생성한 원본 값)dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

레거시 방식 (client_secret)

client_secret 방식

client_secret 방식도 계속 사용 가능합니다. 클라이언트 환경에 따라 PKCE 방식을 선택하실 수도 있습니다.

파라미터타입필수 여부설명예시
grant_typeString필수요청 타입 (authorization_code 고정)authorization_code
codeString필수/v1/oauth/authorize 플로우에서 발급받은 Authorization Codeabc123def456
client_idString필수DataGSM에서 발급받은 클라이언트 IDyour-client-id
client_secretString필수DataGSM에서 발급받은 클라이언트 시크릿 (반드시 서버에서만 사용)your-client-secret
redirect_uriString필수Authorization Code 발급 시 사용한 리다이렉트 URIhttps://your-app.com/callback

응답

성공 응답 (200 OK)

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "scope": "self:read"
}
필드타입설명
access_tokenStringJWT 형식의 Access Token (1시간 유효)
token_typeString토큰 타입 (Bearer 고정)
expires_inIntAccess Token의 만료 시간 (초 단위, 3600 = 1시간)
refresh_tokenStringRefresh Token (30일 유효)
scopeString발급된 권한 범위 (공백으로 구분)

오류 응답

상태 코드설명원인
400 Bad Request잘못된 요청필수 파라미터 누락, 잘못된 형식, Authorization Code가 만료되었거나 유효하지 않음
401 Unauthorized인증 실패Client ID가 존재하지 않거나 Client Secret이 올바르지 않음

요청 예시

cURL (PKCE 방식)

curl -X POST "https://oauth.data.hellogsm.kr/v1/oauth/token" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "abc123def456",
    "client_id": "your-client-id",
    "redirect_uri": "https://your-app.com/callback",
    "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
  }'

응답 예시

성공

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
  "scope": "self:read"
}

실패 (400 Bad Request)

{
  "error": "invalid_grant",
  "error_description": "Authorization Code가 유효하지 않거나 만료되었습니다."
}

실패 (401 Unauthorized)

{
  "error": "invalid_client",
  "error_description": "클라이언트 인증에 실패했습니다."
}

사용 예제

다음은 여러 언어에서 토큰을 교환하는 예제입니다.

async function exchangeToken(code, codeVerifier, clientId, redirectUri) {
try {
  const response = await fetch('https://oauth.data.hellogsm.kr/v1/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      code,
      client_id: clientId,
      redirect_uri: redirectUri,
      code_verifier: codeVerifier,
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error_description || 'Failed to exchange token');
  }

  const data = await response.json();
  return {
    accessToken: data.access_token,
    refreshToken: data.refresh_token,
    expiresIn: data.expires_in,
  };
} catch (error) {
  console.error('Error:', error.message);
  throw error;
}
}

// 사용 예시 (PKCE)
const codeVerifier = sessionStorage.getItem('oauth_code_verifier');
const tokens = await exchangeToken(
'abc123def456',
codeVerifier,
'your-client-id',
'https://your-app.com/callback'
);
console.log('Access Token:', tokens.accessToken);
console.log('Refresh Token:', tokens.refreshToken);
console.log('Expires In:', tokens.expiresIn, 'seconds');

보안 주의사항

PKCE Code Verifier 보호

PKCE 방식을 사용할 때는 code_verifier를 안전하게 관리해야 합니다:

  • 권장: httpOnly 쿠키에 저장 (BFF 패턴 사용 시)
  • 권장: 서버 세션에 저장
  • ⚠️ 주의: sessionStorage 사용 시 XSS 취약점에 주의
  • 금지: localStorage는 XSS 공격에 매우 취약

code_verifier는 일회성으로 사용되며, 토큰 교환 후 즉시 삭제해야 합니다.

HTTPS 필수

모든 OAuth 통신은 반드시 HTTPS를 사용해야 합니다. HTTP를 사용하면 Authorization Code와 토큰이 평문으로 전송되어 중간자 공격에 취약합니다.

토큰 저장

  • Access Token: sessionStorage 또는 메모리에 저장 (1시간 후 만료)
  • Refresh Token: HttpOnly 쿠키 또는 서버 세션에 저장 (30일 후 만료)
  • localStorage는 XSS 공격에 취약하므로 사용을 지양하세요.

레거시 방식 (client_secret) 보안 주의사항

레거시 client_secret 방식을 사용해야 하는 경우, 다음 사항을 반드시 준수하세요:

Client Secret 보호 (가장 중요):

  • 절대 금지: 프론트엔드 코드에 하드코딩
  • 절대 금지: 브라우저의 localStorage, sessionStorage에 저장
  • 절대 금지: Git 저장소에 커밋
  • 필수: 서버의 환경 변수로 관리
  • 권장: 보안 볼트(AWS Secrets Manager, HashiCorp Vault 등) 사용
# 환경 변수 설정 예시
export DATAGSM_CLIENT_SECRET=your-client-secret

# .env 파일 (Git에 커밋 금지!)
DATAGSM_CLIENT_SECRET=your-client-secret

참고: client_secret 방식을 사용하시는 경우, 여유가 되시면 PKCE 방식으로 전환해보시는 것도 고려해보세요.

다음 단계

Token 교환에 성공했다면, 다음 작업을 수행할 수 있습니다.

실전 구현 예제가 필요하면 Examples 기술 문서를 참고하세요.