auth.md: AI 에이전트를 위한 회원가입 프로토콜

auth.md: AI 에이전트를 위한 회원가입 프로토콜

요즘 AI 에이전트가 단순히 질문에 답변만 하는 게 아니라, 실제로 사용자를 대신해서 외부 서비스를 호출하는 경우가 점점 늘고 있는데요.

예를 들어 코딩 에이전트한테 “Cloudflare에 새 워커를 배포해줘”라고 시키거나, 이메일 에이전트한테 “Resend로 뉴스레터 발송해줘”라고 부탁할 수 있습니다. 그런데 여기서 한 가지 문제가 생깁니다. 에이전트가 그 서비스에 가입되어 있어야 API를 호출할 수 있는데, 가입 자체가 사람을 전제로 만들어진 절차란 말이죠. 회원가입 폼, 이메일 인증, CAPTCHA, 대시보드 로그인 같은 단계를 에이전트가 하나하나 거치게 만드는 건 우스꽝스럽기도 하고 보안적으로도 영 께름칙합니다.

이 문제를 풀려고 WorkOS가 2026년 5월에 auth.md라는 개방형 프로토콜을 공개했습니다. 이름에서 짐작하셨겠지만, 도메인 루트에 마크다운 파일 하나 올려두면 에이전트가 그걸 읽고 알아서 가입을 진행하는 방식입니다. 이 글에서는 auth.md가 왜 등장했는지, 어떤 두 가지 등록 흐름을 제공하는지, 그리고 실제로 어떻게 구현하면 되는지 살펴보겠습니다.

왜 OAuth만으로는 부족한가요?

이런 의문이 먼저 들 수 있습니다. “이미 OAuth가 있는데 또 새로운 프로토콜이 왜 필요한가요?” 충분히 합리적인 질문인데요.

OAuth는 본질적으로 “이미 가입된 사용자가 제3자 앱에게 권한을 위임”하는 시나리오를 다룹니다. 사용자가 구글 계정으로 어떤 서비스에 로그인하는 그림이 OAuth가 가장 잘 하는 일이죠. 그런데 에이전트가 마주하는 상황은 좀 다릅니다. 사용자가 아직 그 서비스에 계정이 없는 상태에서, 에이전트가 사용자 대신 처음부터 가입을 진행해야 하는 경우가 많거든요.

기존에는 이걸 처리하는 방법이 크게 둘이었습니다. 하나는 사용자가 직접 가입한 다음 API 키를 발급받아서 에이전트에게 붙여넣어주는 방식인데요. 사용자 입장에선 번거롭고, 에이전트 입장에서도 매번 사람의 개입을 요구해야 해서 자동화의 의미가 퇴색됩니다. 다른 하나는 에이전트 제공자가 각 서비스와 일일이 파트너십을 맺고 사전에 통합해두는 방식입니다. 이쪽은 확장성이 없죠. 새로운 서비스가 등장할 때마다 양쪽이 따로 협상해야 하니까요.

auth.md는 이 사이의 빈틈을 메우려는 시도입니다. 서비스가 “우리는 이런 방식으로 에이전트 가입을 받습니다”라고 공개적으로 선언하고, 에이전트는 그 선언을 읽어서 표준화된 절차대로 가입을 진행합니다. 어느 한쪽이 다른 한쪽에 종속되지 않고, 중앙에서 누가 조율하지 않아도 동작하는 구조죠.

auth.md의 동작 원리

auth.md는 서비스의 도메인 루트에 마크다운 파일 하나로 시작합니다. 예를 들어 https://api.acme.com/auth.md 같은 경로에 파일을 올려두면, 에이전트가 그 파일을 가져가서 가입 방법을 파악합니다.

전체 흐름은 대략 이렇습니다. 에이전트가 API를 호출하면 서비스는 401 응답으로 “당신은 인증되지 않았다”는 신호와 함께 메타데이터 URL을 알려줍니다. 에이전트는 그 메타데이터를 읽어서 auth.md 위치를 찾고, auth.md를 가져와서 어떤 등록 방식이 지원되는지 확인합니다. 그다음 적절한 흐름을 골라 가입을 진행하면 서비스가 API 키나 액세스 토큰 같은 자격증명을 돌려주는 식이죠.

여기서 흥미로운 부분은 auth.md가 완전히 새로운 표준을 만든 게 아니라 기존 OAuth 위에 얹혀 있다는 점입니다. 메타데이터 발견은 OAuth Protected Resource Metadata (RFC 9728)를 그대로 쓰고, 신원 증명은 IETF에서 표준화 중인 ID-JAG(Identity Assertion Authorization Grant)를 활용합니다. 새로운 암호 알고리즘이나 키 배포 메커니즘을 추가로 도입할 필요가 없다는 뜻이죠.

두 가지 등록 흐름

auth.md는 두 가지 등록 흐름을 정의합니다. 서비스가 둘 다 지원해도 되고, 상황에 맞게 하나만 골라서 지원해도 됩니다.

첫 번째는 Agent Verified입니다. 에이전트의 신원 제공자(예: ChatGPT, Claude 같은 플랫폼)가 사용자를 보증해주는 방식인데요. 사용자가 에이전트 플랫폼에서 이미 검증된 이메일을 갖고 있다면, 플랫폼이 “이 에이전트는 이 사용자를 대행한다”고 서명한 토큰을 발급해줍니다. 에이전트는 그 토큰을 서비스에 제출하고, 서비스는 서명을 검증한 뒤 즉시 자격증명을 발급합니다. 사람의 개입 없이 한 번에 끝나는 흐름이죠.

두 번째는 User Claimed입니다. 에이전트 제공자가 신원을 보증해줄 수 없는 경우나 서비스가 직접 사용자에게 확인을 받고 싶을 때 쓰는 방식인데요. 에이전트가 사용자의 이메일을 제출하면 서비스가 OTP를 발송하고, 사용자가 그 OTP를 에이전트에게 알려주면 가입이 완료됩니다. 우리가 평소 모바일 앱에서 본인 인증할 때 쓰는 흐름이랑 비슷하죠.

Agent Verified 흐름 자세히 보기

Agent Verified는 ID-JAG라는 토큰을 중심으로 돌아갑니다. ID-JAG는 JWT의 한 종류인데, “특정 에이전트가 특정 사용자를 대행해서 특정 대상에게 접근한다”는 사실을 발급자(에이전트 플랫폼)가 서명으로 보증합니다.

토큰 페이로드는 대략 이런 모양입니다.

ID-JAG payload
{
  "iss": "https://platform.example.com",
  "sub": "user_abc123",
  "aud": "https://api.acme.com/",
  "client_id": "agent_xyz789",
  "email": "alice@example.com",
  "email_verified": true,
  "iat": 1748600000,
  "exp": 1748603600,
  "jti": "01HXYZ..."
}

iss는 토큰을 발급한 에이전트 플랫폼이고, sub는 그 플랫폼 내에서의 사용자 고유 ID입니다. aud는 이 토큰이 어느 서비스에 제출될지를 명시하는데, 이게 있어야 토큰을 도용해서 엉뚱한 서비스에 들이미는 공격을 막을 수 있습니다. emailemail_verified는 신원 식별과 검증 상태를 나타내고요.

에이전트는 이 토큰을 받아서 서비스의 /agent-auth 엔드포인트에 POST 요청을 보냅니다.

에이전트 → 서비스
curl -X POST https://api.acme.com/agent-auth \
  -H "Content-Type: application/json" \
  -d '{
    "type": "identity_assertion",
    "assertion": "eyJhbGciOiJSUzI1NiIs...",
    "scopes": ["read:workers", "write:workers"]
  }'

서비스 쪽에서는 이 토큰을 받아 서명을 검증해야 합니다. Node.js로 구현한다면 jose 라이브러리를 쓰는 게 가장 간단합니다.

서비스 측 토큰 검증
import { jwtVerify, createRemoteJWKSet } from "jose";

const jwks = createRemoteJWKSet(
  new URL("https://platform.example.com/.well-known/jwks.json"),
);

const { payload } = await jwtVerify(assertion, jwks, {
  audience: "https://api.acme.com/",
  typ: "oauth-id-jag+jwt",
});

if (!payload.email_verified) {
  throw new Error("missing_verified_email");
}

const user = await findOrCreateUser(payload.email, payload.iss, payload.sub);
const credential = await issueCredential(user, requestedScopes);

검증 과정에서 신뢰할 수 있는 발급자 목록을 미리 관리해두고, iss 값이 그 목록에 있는지 확인하는 단계가 꼭 필요합니다. 아무 플랫폼이나 ID-JAG를 발급해서 자격증명을 받아 가면 안 되니까요. 이 대목은 OAuth 엔드포인트 글에서 강조한 issuer 검증과 같은 원칙입니다.

검증이 통과되면 서비스는 즉시 자격증명을 응답에 담아 돌려줍니다.

서비스 → 에이전트
{
  "credential_type": "api_key",
  "credential": "sk_live_acme_...",
  "scopes": ["read:workers", "write:workers"],
  "expires_in": null
}

자격증명 형식은 서비스 마음대로 정할 수 있습니다. 단기 액세스 토큰을 줘도 되고, 오래 가는 API 키를 줘도 됩니다. 어차피 그 다음 API 호출은 서비스가 원래 갖고 있던 인증 메커니즘으로 처리되니까요.

User Claimed 흐름 자세히 보기

User Claimed는 좀 더 전통적인 OTP 흐름을 따릅니다. 에이전트 제공자가 신원을 보증해주지 않더라도 사용자 본인이 이메일로 확인해주는 방식이죠.

먼저 에이전트가 청구를 시작합니다.

청구 시작
curl -X POST https://api.acme.com/agent-auth/claim \
  -H "Content-Type: application/json" \
  -d '{
    "type": "email_claim",
    "email": "alice@example.com",
    "scopes": ["read:workers"]
  }'

서비스는 claim_token을 생성해서 응답으로 돌려주고, 동시에 사용자 이메일로 6자리 OTP를 발송합니다.

서비스 → 에이전트
{
  "claim_token": "ct_...",
  "expires_in": 600,
  "delivery": "email"
}

사용자가 메일함을 열어서 OTP를 확인한 뒤 에이전트에게 그 숫자를 알려줍니다. 에이전트는 그걸 받아 다시 서비스에 제출합니다.

청구 완료
curl -X POST https://api.acme.com/agent-auth/claim/complete \
  -H "Content-Type: application/json" \
  -d '{
    "claim_token": "ct_...",
    "otp": "428193"
  }'

OTP가 맞으면 서비스는 자격증명을 발급합니다. 이 흐름의 변형으로 “익명 시작” 패턴도 있는데요. 에이전트가 일단 이메일 없이 가입해서 제한된 권한의 자격증명을 받아두고, 나중에 사용자가 OTP로 청구해서 권한을 업그레이드하는 방식입니다. 데모나 미리보기 같은 시나리오에 잘 맞습니다.

auth.md 파일은 어떻게 생겼나요?

이름이 auth.md인 만큼 실제로 마크다운 파일이긴 한데, 형식은 사람이 읽기 좋게 쓰면서도 에이전트가 파싱하기 쉽도록 구조화되어 있습니다.

auth.md
# Acme Agent Auth

Acme API에 에이전트를 등록하려면 이 문서를 따르세요.

## Discovery

메타데이터는 다음 위치에 있습니다.

`https://api.acme.com/.well-known/oauth-protected-resource`

## Supported flows

- `identity_assertion` (ID-JAG)
- `email_claim` (OTP)

## Endpoints

- 등록: `POST https://api.acme.com/agent-auth`
- 청구 시작: `POST https://api.acme.com/agent-auth/claim`
- 청구 완료: `POST https://api.acme.com/agent-auth/claim/complete`

## Trusted providers

다음 발급자의 ID-JAG를 신뢰합니다.

- `https://platform.openai.com`
- `https://claude.ai`
- `https://platform.example.com`

## Credentials

- 유형: `api_key`
- 만료: 영구 (사용자가 수동으로 폐기)
- 스코프: `read:workers`, `write:workers`, `read:logs`

핵심 메타데이터는 별도로 /.well-known/oauth-protected-resource 경로의 JSON 파일에도 발행해두는데, 에이전트는 둘 중 편한 쪽을 골라 읽으면 됩니다. 사람이 직접 가이드처럼 읽고 싶을 땐 auth.md를, 프로그래밍적으로 파싱하고 싶을 땐 JSON을 보는 거죠.

/.well-known/oauth-protected-resource
{
  "resource": "https://api.acme.com/",
  "agent_auth": {
    "auth_md_uri": "https://api.acme.com/auth.md",
    "methods": ["identity_assertion", "email_claim"],
    "agent_auth_uri": "https://api.acme.com/agent-auth",
    "claim_uri": "https://api.acme.com/agent-auth/claim",
    "trusted_issuers": ["https://platform.openai.com", "https://claude.ai"]
  }
}

서비스를 어떻게 통합하나요?

서비스 측에서 auth.md를 도입하려면 대략 다섯 단계가 필요합니다.

우선 도메인 루트에 auth.md 파일을 올립니다. 정적 파일이어도 되고, 동적으로 생성해도 됩니다. 어차피 자주 바뀌지 않을 내용이니 CDN에 캐싱해두면 깔끔합니다.

다음으로 /.well-known/oauth-protected-resource 경로에 JSON 메타데이터를 발행합니다. 이건 oauth-metadata 표준을 그대로 따르고, 그 안에 agent_auth 객체만 추가하면 됩니다.

그리고 인증되지 않은 요청에 401을 반환할 때 WWW-Authenticate 헤더에 메타데이터 위치를 포함시킵니다.

401 응답 헤더
WWW-Authenticate: Bearer resource_metadata="https://api.acme.com/.well-known/oauth-protected-resource"

이제 핵심인 /agent-auth 엔드포인트를 구현합니다. 요청 바디의 type 필드를 보고 분기하면 됩니다. identity_assertion이면 JWKS 기반으로 JWT를 검증하고, email_claim이면 OTP 상태 머신을 돌리는 식이죠.

마지막으로 자격증명을 발급합니다. 기존에 사용자 가입 시 발급하던 API 키나 액세스 토큰을 그대로 재활용하면 됩니다. 새로운 자격증명 형식을 만들 필요는 없습니다.

다섯 단계라고 했지만 실제로는 기존 OAuth 구현이 있다면 추가 작업이 그리 많지 않습니다. 발견 메커니즘과 JWT 검증은 oauth-endpoints 글에서 다룬 것과 거의 동일하고요.

폐기와 보안

에이전트에게 발급한 자격증명도 결국 사용자에게 묶인 권한이기 때문에, 사용자가 언제든 회수할 수 있어야 합니다. auth.md는 두 가지 폐기 경로를 권장합니다.

하나는 서비스 자체 대시보드에서 에이전트별로 자격증명을 폐기하는 방식입니다. 이건 기존에 사용자가 자기 계정에서 API 키를 관리하던 UI를 그대로 재활용하면 됩니다.

다른 하나는 에이전트 제공자가 OIDC Back-Channel Logout 표준을 통해 폐기 이벤트를 푸시하는 방식입니다. 사용자가 ChatGPT의 연결 관리 페이지에서 “이 에이전트의 Acme 연결 해제”를 누르면, ChatGPT가 Acme에게 폐기 이벤트를 보내고 Acme가 해당 자격증명을 즉시 무효화하는 거죠.

보안 측면에서 한 가지 더 신경 써야 할 부분이 있습니다. 신뢰할 수 있는 발급자 목록을 너무 느슨하게 관리하면 위험합니다. 아무 플랫폼이나 ID-JAG를 발급해서 자격증명을 받아 갈 수 있게 되니까요. 반대로 너무 빡빡하게 잠가두면 새로운 에이전트 플랫폼이 등장할 때마다 매번 수동으로 추가해야 하는 운영 부담이 생깁니다. 이건 Auth0WorkOS처럼 IdP를 중개하는 서비스가 빈자리를 채워줄 수 있는 영역이기도 합니다.

어떤 서비스가 이미 쓰고 있나요?

WorkOS가 발표한 시점에 이미 Cloudflare, Firecrawl, Resend, Monday.com이 auth.md를 채택해서 운영 중입니다. 모두 에이전트 기반 자동화 수요가 큰 서비스들이죠. Cloudflare는 워커 배포, Firecrawl은 웹 스크래핑, Resend는 이메일 발송, Monday.com은 프로젝트 관리 자동화에서 에이전트와 자연스럽게 연결됩니다.

흥미로운 점은 이 서비스들이 WorkOS를 직접 쓰지 않고도 auth.md를 채택했다는 겁니다. 프로토콜이 완전히 개방되어 있어서 누구든 자신의 인프라 위에 구현할 수 있고, 어느 에이전트 플랫폼에도 종속되지 않는 구조입니다. 이건 llms.txt가 robots.txt의 AI 버전으로 자리 잡은 방식이나 AGENTS.md가 도구 중립적인 표준이 된 방식과 비슷한 흐름이라고 볼 수 있습니다.

물론 아직 초기 단계이고 사양도 IETF에서 한창 논의 중인 ID-JAG에 기대고 있어서 세부 항목은 더 다듬어질 여지가 있습니다. 하지만 에이전트 생태계가 빠르게 커지는 지금, “에이전트가 사용자를 대신해 가입한다”는 가장 원초적인 동작을 표준화하려는 첫 번째 진지한 시도라는 점에서 충분히 눈여겨볼 만합니다.

마치며

auth.md는 새로운 보안 모델을 처음부터 만든 게 아니라, 이미 잘 동작하는 OAuth 위에 “에이전트 가입”이라는 한 겹을 얹은 프로토콜입니다. 그래서 도입 비용이 낮고, 기존에 OAuth를 잘 운영해왔다면 며칠이면 통합할 수 있습니다.

가장 큰 의의는 에이전트와 서비스 사이에 끼어 있던 사람의 개입을 줄여준다는 점입니다. 사용자가 회원가입 폼을 채우고 API 키를 복사해서 에이전트에 붙여넣는 단계가 사라지면, 에이전트가 정말로 “도구”처럼 동작할 수 있는 환경이 만들어집니다. 동시에 사용자가 폐기 권한을 잃지 않기 때문에 권한 위임의 통제력도 유지됩니다.

만약 본인이 운영하는 서비스가 에이전트의 자동화 대상이 될 만한 곳이라면 auth.md를 한번 검토해보시는 걸 추천합니다. 기존 API 키 발급 흐름에 끼워 넣는 정도로 시작할 수 있고, 채택하지 않더라도 어떤 변화가 일어나고 있는지 이해하는 데 도움이 됩니다.

더 자세한 사양은 auth.md 공식 사이트에서 확인할 수 있습니다.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord