AI 에이전트를 위한 OAuth Token Vault 패턴

발행: (2026년 3월 31일 오후 01:50 GMT+9)
15 분 소요
원문: Dev.to

I’m happy to translate the article for you, but I’ll need the full text you’d like translated. Could you please paste the content (excluding the source line you’ve already provided) here? Once I have it, I’ll keep the source link at the top and translate the rest into Korean while preserving all formatting, markdown, and technical terms.

문제

Your AI agent needs to:

  • OAuth를 통해 사용자 인증
  • 액세스 토큰을 안전하게 저장
  • 토큰이 만료되면 갱신
  • 실행 시 에이전트가 해당 토큰을 사용하도록 함

The naive approach looks like this:

// DON'T DO THIS
const user = await db.users.findOne({ id: userId });
const githubToken = user.github_access_token; // stored in your DB
const repos = await fetch('https://api.github.com/user/repos', {
  headers: { Authorization: `Bearer ${githubToken}` }
});

왜 이것이 문제인가

  • 데이터베이스가 자격 증명 저장소가 된다. 침해가 발생하면 사용자 토큰이 모두 유출된다.
  • 토큰 갱신 로직이 애플리케이션에 존재한다. 이제 모든 제공자에 대한 갱신 흐름을 유지해야 한다.
  • 언제 어떤 토큰이 사용됐는지에 대한 감사 로그가 없다.
  • 세분화된 폐기가 불가능하다. 사용자는 GitHub 접근 권한만을 취소할 수 없으며, 모든 권한을 동시에 취소해야 한다.

Source:

토큰 볼트 패턴

핵심 아이디어: OAuth 토큰을 애플리케이션에 절대 저장하지 마세요. 이 작업을 위해 정확히 설계된 아이덴티티 제공자에게 자격 증명 저장을 위임합니다.

흐름도

User ──► Your App ──► Identity Provider (stores tokens)


Agent needs GitHub ──► Token Exchange (RFC 8693) ──► Fresh token ──► GitHub API

애플리케이션이 GitHub 토큰을 보관하는 대신, 아이덴티티 제공자가 토큰을 보관합니다. 에이전트가 GitHub에 호출해야 할 때는 RFC 8693 토큰 교환을 통해 자체 세션 토큰을 범위가 지정된 단기간 유효 GitHub 토큰으로 교환합니다. 이 토큰은 데이터베이스에 절대 저장되지 않습니다.

RFC 8693 토큰 교환 실전

RFC 8693은 한 보안 토큰을 다른 토큰으로 교환하는 표준 방식을 정의합니다. 이 맥락에서:

역할설명
Subject token에이전트가 이미 보유하고 있는 사용자 세션 토큰
Requested token새로 발급받은 GitHub 액세스 토큰
Exchange endpointID 공급자가 제공함

교환 흐름:

// Token exchange request
const response = await fetch(`${AUTH_DOMAIN}/oauth/token`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
    subject_token: userSessionToken,
    subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
    requested_token_type: 'urn:ietf:params:oauth:token-type:access_token',
    audience: 'github',
  }),
});

const { access_token } = await response.json();
// `access_token` is a fresh, short‑lived GitHub token

ID 공급자가 처리하는 내용:

  • 기본 OAuth 리프레시 토큰의 안전한 저장
  • 토큰이 만료될 때 자동 토큰 갱신
  • 스코프 검증 (에이전트는 사용자가 허용한 범위만 접근 가능)
  • 모든 토큰 교환에 대한 감사 로그 기록

Source:

Auth0 Token Vault를 이용한 구현

Auth0는 이 사용 사례를 위해 특별히 Token Vault를 제공합니다. 아래는 GitHub, Google Calendar, Slack에서 데이터를 가져오는 AI 에이전트에 이를 연결하는 최소 예시입니다.

1. 토큰 요구 사항을 정의한 도구 설정

각 AI 도구는 필요한 연결을 선언합니다:

import { getAccessTokenForConnection } from '@auth0/ai-vercel';

const githubTool = tool({
  description: '인증된 사용자의 최신 풀 리퀘스트를 가져옵니다',
  parameters: z.object({
    repo: z.string().optional(),
  }),
  execute: async ({ repo }) => {
    // 토큰 교환은 실행 시점에 이루어집니다
    const token = await getAccessTokenForConnection('github');

    const url = repo
      ? `https://api.github.com/repos/${repo}/pulls?state=all&per_page=10`
      : 'https://api.github.com/user/repos?sort=updated&per_page=5';

    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${token}`,
        Accept: 'application/vnd.github+json',
      },
    });

    return response.json();
  },
});

핵심 상세: getAccessTokenForConnection('github')은 내부적으로 RFC 8693 교환을 수행합니다. 토큰이 애플리케이션에 저장되지 않으며, 별도의 리프레시 로직이 필요하지 않습니다.

2. AI SDK에 도구 연결

Vercel AI SDK를 사용하면, 도구들을 streamText에 바로 플러그인할 수 있습니다:

import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

const result = streamText({
  model: anthropic('claude-sonnet-4-5-20250514'),
  system: `당신은 개발자 어시스턴트입니다. 답변하기 전에 반드시 
           사용 가능한 도구를 통해 실제 데이터를 가져오세요. 절대 추측하지 마세요.`,
  messages: conversationHistory,
  tools: {
    github_prs: githubTool,
    calendar_events: calendarTool,
    slack_messages: slackTool,
  },
  maxSteps: 5,
});

모델이 GitHub 데이터가 필요하다고 판단하면, 도구가 실행되고 토큰 교환이 투명하게 이루어지며 모델은 실제 데이터를 받게 됩니다. 사용자의 GitHub 토큰은 로그, 데이터베이스, 애플리케이션 코드 어디에도 나타나지 않습니다.

3. 권한 경계

사용자는 표준 OAuth 동의 흐름을 통해 서비스에 연결합니다. Token Vault는 결과 자격 증명을 저장합니다. 애플리케이션은 자격 증명 자체가 아니라 연결에 대한 참조만 저장합니다.

// 사용자가 GitHub을 연결함
// 리디렉션: /auth/connect/github
// Auth0가 OAuth 흐름을 처리하고 토큰을 Token Vault에 저장
// 애플리케이션이 받는 데이터: { connection: 'github', status: 'connected' }
// 애플리케이션은 원시 토큰을 저장하지 않음

TL;DR

  1. OAuth 토큰을 자체 데이터베이스에 절대 저장하지 마세요.
  2. 스토리지와 토큰 갱신을 아이덴티티 제공자(예: Auth0 Token Vault)에게 위임하세요.
  3. 런타임에 단기간 유효한 액세스 토큰을 얻기 위해 RFC 8693 토큰 교환을 사용하세요.
  4. 교환 과정을 AI 도구 레이어에 통합하여 모델이 필요할 때만 최신 토큰을 받도록 하세요.

Benefits

  • 보안 강화 (데이터베이스에서 자격 증명이 유출되지 않음)
  • 중앙 집중식 감사 로그 및 토큰 폐기
  • 코드 단순화 (리프레시 토큰 처리 불필요)
  • 사용자 및 도구별 세밀한 스코프 적용

AI 에이전트가 필요로 하는 모든 서드파티 통합에 적용하면, 사용자와 인프라 모두를 보다 안전하게 보호할 수 있습니다.

가져오기: 실제 토큰

Enter fullscreen mode
Exit fullscreen mode

각 도구 호출은 감사 이벤트를 생성합니다:

  • Who 토큰을 요청한 사람 (사용자 ID)
  • Which 접근된 연결 (GitHub, Slack 등)
  • When 교환이 발생한 시점
  • What 사용된 범위

사용자가 GitHub 접근을 취소하면, Token Vault는 저장된 자격 증명을 무효화합니다. GitHub가 필요한 다음 도구 호출은 “재연결 필요” 메시지와 함께 깔끔하게 실패합니다.

프로덕션에서의 모습

저는 이 패턴을 사용해 DevContext를 만들었습니다. 이것은 GitHub, Google Calendar, Slack에 연결하여 개발자 브리핑을 생성하는 AI 어시스턴트입니다. “어제 무엇을 작업했나요?” 라고 물어보면 다음과 같이 동작합니다:

  1. GitHub 도구를 호출합니다 (GitHub용 토큰 교환)
  2. Calendar 도구를 호출합니다 (Google용 토큰 교환)
  3. Slack 도구를 호출합니다 (Slack용 토큰 교환)
  4. 실제 데이터를 기반으로 브리핑을 종합합니다

결과: 세 개의 별도 토큰 교환, 세 개의 별도 감사 이벤트, 데이터베이스에 토큰이 전혀 없습니다. 내 Supabase 인스턴스가 내일 침해된다면, 공격자는 사용자 프로필과 설정만 찾을 수 있을 것이며 OAuth 토큰은 없습니다.

언제 이것을 사용하고 토큰을 직접 저장할지

Token Vault를 사용해야 할 경우:

  • 에이전트가 사용자당 여러 타사 API에 접근할 때
  • 규제 산업(헬스케어, 금융)에서 자격 증명 처리가 감사 대상일 때
  • 사용자가 개별 서비스 연결을 해제할 수 있기를 원할 때
  • 여러 OAuth 제공자를 위한 토큰 갱신 로직을 구축·유지하고 싶지 않을 때

토큰을 직접 저장해야 할 경우:

  • 단일 서비스에 간단한 API 키(오Auth 아님)로 접근할 때
  • 프로토타입을 만들고 보안 수준이 아직 중요하지 않을 때
  • 사용 중인 ID 제공자가 토큰 교환을 지원하지 않을 때

중요한 지루한 부분

실제 디버깅 세션을 야기한 몇 가지 구현 세부 사항:

  • System‑prompt 강제 적용 – 모델은 도구를 전혀 호출하지 않고도 스탠드‑업 요약을 자유롭게 환상할 수 있습니다. 시스템 프롬프트에 “you MUST call tools before responding”을 포함시켜 도구 사용을 강제하세요. 더 나은 방법은 응답을 스트리밍하기 전에 서버에서 도구 호출이 이루어졌는지 검증하는 것입니다.
  • Token‑exchange 지연 – 각 RFC 8693 교환은 약 100–200 ms를 추가합니다. 에이전트가 세 개의 도구를 호출하면 300–600 ms의 오버헤드가 발생합니다. 스트리밍 응답에서는 눈에 띄지 않지만, 지연에 민감한 애플리케이션에서는 알아두면 좋습니다.
  • Connection‑status 캐싱 – GitHub이 여전히 연결되어 있는지 매 페이지 로드 시마다 Token Vault를 확인하지 마세요. 연결 상태를 클라이언트 측에 캐시하고, 도구 호출이 “reconnect” 오류와 함께 실패할 때만 다시 확인합니다.
  • 연결이 없을 때의 폴백 – 사용자가 Slack을 연결하지 않은 경우, Slack 도구는 에이전트를 충돌시키는 오류를 발생시키는 대신 유용한 메시지를 반환해야 합니다(예: “Slack not connected. Connect it in Settings to include Slack data in briefings.”).

Summary

패턴은 간단합니다:

  1. 토큰 볼트 지원이 있는 아이덴티티 제공자 사용 (Auth0, 혹은 RFC 8693으로 직접 구현).
  2. 각 AI 도구를 필요한 연결과 함께 정의합니다.
  3. 로그인 시점이 아니라 실행 시점에 토큰 교환을 수행합니다.
  4. 애플리케이션은 OAuth 토큰을 절대 저장·조회·로그하지 않습니다.
  5. 사용자는 어떤 서비스가 연결되는지 세밀하게 제어할 수 있습니다.
  6. 모든 자격 증명 접근에 대한 감사 로그를 확보합니다.

데이터베이스에 토큰 제로. 코드베이스에 리프레시 로직 제로. 데이터베이스 침해 시 자격 증명 유출 제로.

이러한 지루한 인프라가 바로 흥미로운 사고 대응을 방지합니다.

저는 프로덕션 AI 시스템을 구축합니다. 비슷한 작업을 하고 계시다면 **astraedus.dev**에서 만나세요. 제 책 Production AI Agents 은 이러한 패턴을 깊이 있게 다룹니다.

0 조회
Back to Blog

관련 글

더 보기 »

앱에서 Google 계정 사용자 이름 변경 지원

2026년 4월 2일 — 3월 31일 현재, 미국 사용자는 이제 Google Account 사용자 이름을 업데이트하면서 계정, 인박스 및 데이터를 완전히 유지할 수 있습니다. 만약 귀하의 pl...

앱에서 Google 계정 사용자 이름 변경 지원

2023년 3월 31일 현재, 미국 사용자는 이제 Google Account 사용자 이름을 업데이트하면서 계정, 인박스 및 데이터를 완전히 유지할 수 있습니다. 만약 귀하의 플랫폼이 Si...