수정: `xurl` OAuth 2.0이 X API에서 'unauthorized_client' 오류로 실패
Source: Dev.to
xurl을 사용해 X API에 인증하고 다음 오류가 표시되는 경우:
OAuth2 authentication failed: Auth Error: TokenExchangeError
(cause: oauth2: "unauthorized_client" "Missing valid authorization header")해결 방법은 X 개발자 포털의 한 가지 설정을 변경하는 것입니다.
왜 이런 일이 발생하는가
xurl은 OAuth 2.0 PKCE 흐름을 사용합니다. 이 흐름은 공개 클라이언트(모바일 앱, CLI, SPA)를 위해 설계되었습니다. 공개 클라이언트는 토큰 교환 시 요청 본문에 자격 증명을 포함합니다.
X API 앱은 기본적으로 기밀 클라이언트로 생성됩니다. 기밀 클라이언트는 Authorization: Basic 헤더를 통해 자격 증명을 전달해야 하는데, xurl은 이 방식을 사용하지 않습니다. xurl이 해당 헤더 없이 토큰 교환 요청을 보내면 X는 unauthorized_client 오류와 함께 이를 거부합니다.
앱 유형을 확인하려면 클라이언트 ID를 base64 디코딩해 보세요:
echo "YOUR_CLIENT_ID" | base64 -d- 디코딩된 값이
:ci로 끝나면 기밀 클라이언트입니다. :na로 끝나면 네이티브(공개) 클라이언트입니다.
해결 방법
X 앱 유형을 Web App에서 Native App으로 개발자 포털에서 변경합니다.
포털에서:
앱 선택 → User authentication settings → Edit- App type을 Native App으로 설정
- Callback URI를
http://localhost:8080/callback로 설정 - 저장
새 자격 증명을 다시 등록하고
xurl로 인증합니다:# 포털에서 새 자격 증명을 생성한 후 다시 등록 xurl auth apps add my-app \ --client-id YOUR_NEW_CLIENT_ID \ --client-secret YOUR_NEW_CLIENT_SECRET # 베어러 토큰 및 OAuth 1.0a 자격 증명 저장 (선택 사항) xurl auth app --bearer-token YOUR_BEARER_TOKEN xurl auth oauth1 \ --consumer-key YOUR_CONSUMER_KEY \ --consumer-secret YOUR_CONSUMER_SECRET \ --access-token YOUR_ACCESS_TOKEN \ --token-secret YOUR_ACCESS_TOKEN_SECRET # 기본 앱으로 설정 xurl auth default my-app # PKCE 흐름 실행 xurl --app my-app auth oauth2브라우저 기반 동의 흐름을 완료한 후, 모든 것이 정상 작동하는지 확인합니다:
xurl auth status xurl --auth oauth2 /2/users/me # 사용자 컨텍스트 xurl --auth oauth1 /2/users/me # 역시 작동 xurl --auth app "/2/tweets/search/recent?query=hello&max_results=5" # 앱 전용
보너스: 앱 유형을 변경한 후 자격 증명 재생성
앱 유형을 전환하면 X는 :na 접미사가 붙은 새로운 클라이언트 ID를 발급합니다. 이전 :ci ID는 PKCE 흐름에서 더 이상 유효하지 않으므로, xurl로 다시 등록하기 전에 Keys and Tokens 탭에서 새로운 값을 복사하십시오.
TL;DR
| 앱 유형 | 클라이언트 ID 접미사 | xurl OAuth 2.0와 호환 여부? |
|---|---|---|
| 웹 앱 / 봇 | :ci (기밀) | ❌ |
| 네이티브 앱 / SPA | :na (공개) | ✅ |
X 개발자 포털에서 네이티브 앱으로 변경하고, 자격 증명을 다시 생성하면 xurl의 OAuth 2.0 흐름이 정상적으로 작동합니다.
웹 앱은 OAuth 2.0을 지원하나요?
Web App/Bot 앱 은 OAuth 2.0을 지원하지만 xurl을 통해서는 지원되지 않습니다. xurl은 공개 클라이언트 PKCE 흐름만 구현하기 때문입니다. 기밀 클라이언트는 토큰 교환 단계에서 Authorization: Basic 헤더가 필요하며, 이는 대부분의 CLI 도구( xurl 포함)에서 X에 대해 처리하지 못합니다.
curl을 이용한 수동 OAuth 2.0
Step 1 – 인증 URL을 생성하고 브라우저에서 열기:
# 코드 검증자와 챌린지를 생성합니다 (PKCE는 선택 사항이지만 권장됩니다)
VERIFIER=$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-43)
CHALLENGE=$(echo -n "$VERIFIER" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '=')
STATE=$(openssl rand -hex 16)
CLIENT_ID="YOUR_CLIENT_ID"
REDIRECT="http://localhost:8080/callback"
echo "https://x.com/i/oauth2/authorize?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT}&scope=tweet.read%20users.read%20offline.access&state=${STATE}&code_challenge=${CHALLENGE}&code_challenge_method=S256"출력된 URL을 브라우저에서 열고, 인증을 진행한 뒤 리다이렉트 URL에서 code=… 값을 복사합니다.
Step 2 – Basic Auth를 사용해 코드를 교환:
CLIENT_ID="YOUR_CLIENT_ID"
CLIENT_SECRET="YOUR_CLIENT_SECRET"
CODE="CODE_FROM_REDIRECT"
VERIFIER="THE_VERIFIER_FROM_STEP_1"
curl -X POST https://api.x.com/2/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic $(echo -n "${CLIENT_ID}:${CLIENT_SECRET}" | base64 -w 0)" \
-d "grant_type=authorization_code&code=${CODE}&redirect_uri=${REDIRECT}&code_verifier=${VERIFIER}"응답에 포함된 access_token을 바로 xurl과 함께 사용할 수 있습니다:
xurl -H "Authorization: Bearer ACCESS_TOKEN" /2/users/me언제 어떤 것을 사용해야 할까
| 사용 사례 | 앱 유형 | 인증 방법 |
|---|---|---|
| CLI / 로컬 개발 | 네이티브 앱 | xurl oauth2 (PKCE, 비밀키 없음) |
| 서버‑사이드 앱 | 웹 앱 / 봇 | OAuth 2.0 기밀 (Basic Auth, 비밀키는 서버에 보관) |
| 자신으로 게시 | 모든 앱 | OAuth 1.0a (개인용으로 가장 간단) |
| 읽기 전용 공개 데이터 | 모든 앱 | Bearer token (앱 전용) |
Web App/Bot + OAuth 2.0은 클라이언트 비밀키가 서버를 떠나지 않는 서버‑사이드 애플리케이션을 위해 설계되었습니다. 로컬 CLI 사용의 경우 네이티브 앱이 올바른 선택입니다. OAuth 1.0a는 어느 앱 유형과도 함께 사용할 수 있으며, 개인용이나 개발용으로 가장 저항이 적은 경로가 되는 경우가 많습니다.