Skip to content

OAuth 오류 코드

Source: oauth/errors.md · Live: https://docs.1pass.dev/oauth/errors LLM-sanitized: internal links absolutized, VitePress containers → admonitions, line numbers in the Jump-to Index reference this rendered file (1-indexed).

📍 Jump-to Index

  • L40-L61: ## 응답 시그널 한눈에
  • L62-L217: ## 코드별 레퍼런스
    • L67-L82: ### invalid_request
    • L83-L100: ### invalid_client
    • L101-L112: ### unauthorized_client
    • L113-L133: ### invalid_grant
    • L134-L147: ### invalid_scope
    • L148-L158: ### unsupported_grant_type
    • L159-L164: ### unsupported_response_type
    • L165-L172: ### access_denied
    • L173-L188: ### invalid_token
    • L189-L198: ### authorization_pending / slow_down / expired_token
    • L199-L217: ### rate_limited
  • L218-L226: ## 에러 트리아지 워크플로
  • L227-L239: ## 로깅 주의사항
  • L240-L246: ## 참고 RFC

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

💡 Tip: 처음 통합하는 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 모두)
  • 앱이 sandbox tier 인데 localhost/127.0.0.1실도메인 redirect_uri 를 요청 — sandbox tier 는 로컬 호스트만 허용하므로, redirect_uri 가 콘솔에 등록돼 있어도 redirect_uri not registered 로 거절됨. 실도메인으로 가려면 Production 승급 필요
  • 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'))
  4. error_descriptionredirect_uri not registered 인데 콘솔의 Redirect URIs 에 분명히 등록돼 있다면 → 앱 tier 가 sandbox 인지 확인. sandbox 는 localhost/127.0.0.1 만 허용하므로 실도메인은 Production 승급 후 사용 가능

invalid_client

의미: client 인증 실패 — client_id / client_secret 불일치 또는 secret 누락.

HTTP: 401

흔한 원인:

  • 백엔드 없는 모바일/SPA/CLI 앱을 confidential 로 등록함 (가장 흔함) — error_descriptionclient credentials missing 이면 이 케이스. client type 을 명시하지 않으면 기본값이 confidential 이라, secret 을 안전하게 보관할 수 없는 앱이 매 토큰 교환에서 실패한다. → public client (PKCE) 로 등록해야 한다.
  • client_secret 환경변수가 dev/prod 사이에 잘못 매핑됨
  • HTTP Basic auth 헤더의 base64 인코딩 실수 (client_id:client_secret 형태)
  • secret rotate 후 RP 서버 재배포 누락 → 옛 secret 사용
  • (public client 인데) client_secret 또는 HTTP Basic 인증을 보냄 → downgrade 방어로 거절 (public clients must not present client_secret)

빠른 진단:

  1. error_descriptionclient credentials missing 인가? → 콘솔 → 앱 상세 → Client type 확인. 백엔드가 없는데 confidential 이면 public 으로 재등록해야 한다 (client_type 은 생성 후 변경 불가 → 새 RP 등록). Public client 가이드 · Public vs Confidential 결정
  2. 콘솔 → 앱 상세 → "Secret 재발급" → 새 secret 으로 RP env 갱신
  3. PKCE-only RP 라면 secret 자체가 불필요 (PKCE 가이드)

unauthorized_client

의미: client 자체는 인증됐지만 현재 작업 권한이 없음.

흔한 원인:

  • 앱이 pending 상태 (관리자 승인 대기 — localhost 외 도메인일 때)
  • 앱이 suspended 상태 (운영진이 정지함 — 콘솔 → 감사 로그 확인)
  • 앱이 사용한 grant_type 이 등록 시 허용되지 않음

빠른 진단:

  • 콘솔 → 앱 상세 → Status pill 확인. pending 이면 Production 승급 신청을 진행하세요. 승급 신청을 제출해야 앱 상세에 진행 단계 stepper 가 표시됩니다 (신청 전에는 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가 제품의 신뢰를 만듭니다.