Skip to content

OAuth 오류 코드

모든 오류 응답은 RFC 6749 §5.2 포맷을 따릅니다.

json
{
  "error": "<기계 판독 가능 코드>",
  "error_description": "<사람이 읽기 위한 설명>",
  "error_uri": "https://docs.1pass.dev/oauth/errors#<error>",
  "request_id": "8f0c1e7b-..."
}

응답 시그널 한눈에

RP 서버가 받는 신호는 body 와 header 두 채널로 나옵니다. 콘솔(웹)과 같은 request_id 로 연결되도록 설계되어 있어, RP 가 자기 로그에 cross-reference 하기 좋습니다.

채널필드용도
bodyerror기계 판독용 코드 (RFC 6749 §5.2)
bodyerror_description사람이 읽는 1줄 설명
bodyerror_uri이 문서의 해당 코드 앵커
bodyrequest_id콘솔 request log 와 동일 키
headerX-Logi-Request-Idbody 가 비어 있는 경우(예: HEAD)에도 trace 가능
headerX-Logi-Console-Url운영에서 RP 가 인증된 경우, 콘솔 request_logs 딥링크
headerX-Logi-Scope-Drifttoken 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_methodS256 이 아님 (plain 미지원)
  • 필수 파라미터 누락 (response_type, client_id, redirect_uri, code_challenge)

빠른 진단:

  1. 콘솔 → 앱 상세 → "Redirect URIs" 와 RP 가 보낸 URL 을 글자 단위로 비교
  2. trailing slash, 쿼리 스트링, fragment 모두 제거 후 일치하는지 확인
  3. 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 사용

빠른 진단:

  1. 콘솔 → 앱 상세 → "Secret 재발급" → 새 secret 으로 RP env 갱신
  2. 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 mismatchcode_verifier 가 처음 제출한 code_challenge 와 불일치
  • refresh token not found — RT 가 revoke 됐거나 다른 client 로 발행됨
  • refresh token reuse detected; chain revoked재사용 공격 탐지 — 토큰 체인 전체 폐기. 사용자에게 재로그인 강제
  • refresh token expired — RT 30일 경과

빠른 진단:

  1. PKCE verifier 소실이 가장 흔함 — sessionStorage 사용 시 탭 전환/새로고침으로 휘발하는지 확인
  2. redirect_uri 는 authorize 와 token 에 완전히 동일 해야 함 — trailing slash 하나도 다르면 실패
  3. 콘솔 → 앱 상세 → 에러 로그에서 request_id 로 정확한 원인 확인

invalid_scope

의미: 요청한 scope 가 앱에 등록되지 않았거나 비어있음.

흔한 원인:

  • Scope drift 기본 정책(block) — 등록 안 된 scope 가 요청에 하나라도 포함됨
  • allowed_scopes 미설정 상태에서 scope 파라미터 전송
  • 오타 (예: emailemail_address)
  • 커스텀 scope 의 namespace prefix 누락 (<client_id>:reviewer_role 형식)

빠른 진단:

  • 콘솔 → 앱 상세 → "Allowed Scopes" 에 등록된 정확한 이름 확인
  • Scope drift 헤더(X-Logi-Scope-Drift)가 함께 떴다면 Scope drift 정책

unsupported_grant_type

의미: 지원하지 않는 grant_type 사용.

지원하는 값:

  • authorization_code
  • refresh_token
  • urn:ietf:params:oauth:grant-type:device_code (RFC 8628)

password, implicit, client_credentials 는 지원하지 않습니다 (보안상 의도적 미지원).

unsupported_response_type

의미: /oauth/authorizeresponse_typecode 가 아님.

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_tokendevice_code 만료처음부터 다시 시작

rate_limited

의미: rate-limit 한도 초과.

HTTP: 429

한도:

엔드포인트한도
/session5/min (Cloudflare) · 10/3min (Rails)IP
/oauth/token20/minclient_id
/oauth/device_authorization30/minclient_id
/api/v1/me/merge/otp10/minuser_id

대응: Retry-After 헤더 존중, 지수 백오프, 단일 사용자에 대해 토큰 캐시 적극 사용.


에러 트리아지 워크플로

새 통합에서 4xx 가 났을 때 따라갈 절차:

  1. 응답 body 의 error_description 을 RP 서버 로그에 그대로 남기세요. — 가장 흔한 실수의 원인이 여기에 직접 적혀있습니다.
  2. request_id 또는 X-Logi-Request-Id 헤더를 같이 기록하세요. — 콘솔 request_logs 에서 같은 키로 정확히 매칭됩니다.
  3. 운영에선 X-Logi-Console-Url 헤더의 URL 을 클릭하세요. — 해당 요청이 콘솔에 즉시 떠 있습니다.
  4. 이 문서의 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_uri
  • request_id, X-Logi-Request-Id
  • client_id, redirect_uri (PII 아님)

참고 RFC

Identity가 제품의 신뢰를 만듭니다.