OAuth 오류 코드
모든 오류 응답은 RFC 6749 §5.2 포맷을 따릅니다.
{
"error": "<기계 판독 가능 코드>",
"error_description": "<사람이 읽기 위한 설명>",
"error_uri": "https://docs.1pass.dev/oauth/errors#<error>",
"request_id": "8f0c1e7b-..."
}응답 시그널 한눈에
RP 서버가 받는 신호는 body 와 header 두 채널로 나옵니다. 콘솔(웹)과 같은 request_id 로 연결되도록 설계되어 있어, RP 가 자기 로그에 cross-reference 하기 좋습니다.
| 채널 | 필드 | 용도 |
|---|---|---|
| body | error | 기계 판독용 코드 (RFC 6749 §5.2) |
| body | error_description | 사람이 읽는 1줄 설명 |
| body | error_uri | 이 문서의 해당 코드 앵커 |
| body | request_id | 콘솔 request log 와 동일 키 |
| header | X-Logi-Request-Id | body 가 비어 있는 경우(예: HEAD)에도 trace 가능 |
| header | X-Logi-Console-Url | 운영에서 RP 가 인증된 경우, 콘솔 request_logs 딥링크 |
| header | X-Logi-Scope-Drift | token 200 응답에 동봉 — 최근 7일 내 drift 이력이 있을 때. 기본 block 정책에선 drift 요청 자체가 invalid_scope 로 거절되며, 헤더는 log_only/alert 명시 앱 또는 거절·기록된 이력의 echo |
처음 통합하는 RP 라면
응답 body 의 error_description 만 그대로 RP 서버 로그에 INFO 레벨로 기록해두세요. 대부분의 통합 실수는 redirect_uri mismatch / PKCE verifier mismatch 두 개로 설명되며, error_description 이 정확한 원인을 담고 있습니다.
코드별 레퍼런스
각 코드 앵커는 OAuth 응답의 error_uri 가 가리키는 위치입니다. URL 형식: https://docs.1pass.dev/oauth/errors#<error>
invalid_request
의미: 요청 자체가 RFC 6749 형식을 위반했습니다.
흔한 원인:
redirect_uri가 앱 등록 정보의 화이트리스트와 정확히 일치하지 않음 (scheme/host/path/query 모두)code_challenge_method가S256이 아님 (plain미지원)- 필수 파라미터 누락 (
response_type,client_id,redirect_uri,code_challenge)
빠른 진단:
- 콘솔 → 앱 상세 → "Redirect URIs" 와 RP 가 보낸 URL 을 글자 단위로 비교
- trailing slash, 쿼리 스트링, fragment 모두 제거 후 일치하는지 확인
- PKCE 라이브러리가
S256출력하는지 확인 (Buffer.from(sha256(verifier)).toString('base64url'))
invalid_client
의미: client 인증 실패 — client_id / client_secret 불일치 또는 secret 누락.
HTTP: 401
흔한 원인:
client_secret환경변수가 dev/prod 사이에 잘못 매핑됨- HTTP Basic auth 헤더의 base64 인코딩 실수 (
client_id:client_secret형태) - secret rotate 후 RP 서버 재배포 누락 → 옛 secret 사용
빠른 진단:
- 콘솔 → 앱 상세 → "Secret 재발급" → 새 secret 으로 RP env 갱신
- PKCE-only RP 라면 secret 자체가 불필요 (PKCE 가이드)
unauthorized_client
의미: client 자체는 인증됐지만 현재 작업 권한이 없음.
흔한 원인:
- 앱이
pending상태 (관리자 승인 대기 —localhost외 도메인일 때) - 앱이
suspended상태 (운영진이 정지함 — 콘솔 → 감사 로그 확인) - 앱이 사용한
grant_type이 등록 시 허용되지 않음
빠른 진단:
- 콘솔 → 앱 상세 → Status pill 확인.
pending이면 Production 승급 신청 진행 단계 stepper 가 보입니다.
invalid_grant
의미: authorization_code / refresh_token 이 유효하지 않음.
HTTP: 400
흔한 원인 + 정확한 error_description:
code not found— code 가 이미 한 번 교환됐거나 존재한 적 없음code already used— 같은 code 로 두 번째 교환 시도. authorization code 는 1회용code expired— code 발급 후 10분 초과redirect_uri mismatch—/oauth/authorize때와/oauth/token때의 URI 가 다름PKCE verifier mismatch—code_verifier가 처음 제출한code_challenge와 불일치refresh token not found— RT 가 revoke 됐거나 다른 client 로 발행됨refresh token reuse detected; chain revoked— 재사용 공격 탐지 — 토큰 체인 전체 폐기. 사용자에게 재로그인 강제refresh token expired— RT 30일 경과
빠른 진단:
- PKCE verifier 소실이 가장 흔함 —
sessionStorage사용 시 탭 전환/새로고침으로 휘발하는지 확인 redirect_uri는 authorize 와 token 에 완전히 동일 해야 함 — trailing slash 하나도 다르면 실패- 콘솔 → 앱 상세 → 에러 로그에서
request_id로 정확한 원인 확인
invalid_scope
의미: 요청한 scope 가 앱에 등록되지 않았거나 비어있음.
흔한 원인:
- Scope drift 기본 정책(
block) — 등록 안 된 scope 가 요청에 하나라도 포함됨 allowed_scopes미설정 상태에서 scope 파라미터 전송- 오타 (예:
email↔email_address) - 커스텀 scope 의 namespace prefix 누락 (
<client_id>:reviewer_role형식)
빠른 진단:
- 콘솔 → 앱 상세 → "Allowed Scopes" 에 등록된 정확한 이름 확인
- Scope drift 헤더(
X-Logi-Scope-Drift)가 함께 떴다면 Scope drift 정책
unsupported_grant_type
의미: 지원하지 않는 grant_type 사용.
지원하는 값:
authorization_coderefresh_tokenurn:ietf:params:oauth:grant-type:device_code(RFC 8628)
password, implicit, client_credentials 는 지원하지 않습니다 (보안상 의도적 미지원).
unsupported_response_type
의미: /oauth/authorize 의 response_type 이 code 가 아님.
logi 는 Authorization Code Flow 만 지원합니다. token (implicit), id_token 단독은 미지원.
access_denied
의미: 사용자가 동의 화면에서 "거부" 선택, 또는 device flow 에서 거부됨.
HTTP: 302 (authorize) 또는 400 (token)
대응: RP 는 사용자에게 "로그인 취소됨" UX 제공, 재시도 진입점 노출.
invalid_token
의미: /oauth/userinfo 에서 Bearer access_token 검증 실패.
HTTP: 401 + WWW-Authenticate: Bearer error="invalid_token"
흔한 원인:
- JWT 서명 검증 실패 (RP 가 잘못된 JWKS 사용)
- 토큰 만료 (
expires_in경과) - 토큰 명시적 revoke 됨
- 사용자 계정 soft-delete
빠른 진단:
- JWKS 캐시 무효화 후 재시도
- 토큰 발급 시점의
expires_in과 현재 시각 비교
authorization_pending / slow_down / expired_token
의미: Device Authorization Grant (RFC 8628) 폴링 응답.
| error | 의미 | 대응 |
|---|---|---|
authorization_pending | 사용자가 아직 device_code 승인 안 함 | interval 만큼 대기 후 재폴링 |
slow_down | 폴링이 너무 빠름 | interval 을 5초씩 늘려 재폴링 |
expired_token | device_code 만료 | 처음부터 다시 시작 |
rate_limited
의미: rate-limit 한도 초과.
HTTP: 429
한도:
| 엔드포인트 | 한도 | 키 |
|---|---|---|
/session | 5/min (Cloudflare) · 10/3min (Rails) | IP |
/oauth/token | 20/min | client_id |
/oauth/device_authorization | 30/min | client_id |
/api/v1/me/merge/otp | 10/min | user_id |
대응: Retry-After 헤더 존중, 지수 백오프, 단일 사용자에 대해 토큰 캐시 적극 사용.
에러 트리아지 워크플로
새 통합에서 4xx 가 났을 때 따라갈 절차:
- 응답 body 의
error_description을 RP 서버 로그에 그대로 남기세요. — 가장 흔한 실수의 원인이 여기에 직접 적혀있습니다. request_id또는X-Logi-Request-Id헤더를 같이 기록하세요. — 콘솔 request_logs 에서 같은 키로 정확히 매칭됩니다.- 운영에선
X-Logi-Console-Url헤더의 URL 을 클릭하세요. — 해당 요청이 콘솔에 즉시 떠 있습니다. - 이 문서의
error_uri앵커를 따라가세요. — 위 코드별 섹션에 흔한 원인 + 빠른 진단이 적혀있습니다.
로깅 주의사항
절대 로그에 남기지 말 것:
password,client_secret,code_verifier,refresh_token,access_token(JWT 포함),logi_pak_*- logi 자체는
password_digest,otp_secret_encrypted, JWT, PAK plaintext 를 한 번도 로그에 남기지 않습니다.
남겨도 안전한 것:
error,error_description,error_urirequest_id,X-Logi-Request-Idclient_id,redirect_uri(PII 아님)