Skip to content

응답 헤더 / Body 시그널 활용

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

📍 Jump-to Index

  • L31-L70: ## 시그널 카탈로그
    • L33-L45: ### Body 필드 (4xx 응답)
    • L46-L56: ### 응답 헤더 (OAuth 응답)
    • L57-L70: ### Rate-limit 시그널 (429 응답)
  • L71-L141: ## 패턴 1: 모든 4xx 응답을 자동 로깅
    • L73-L98: ### Node.js / Express + axios
    • L99-L119: ### Rails + Faraday
    • L120-L141: ### Python + requests
  • L142-L166: ## 패턴 2: Sentry / DataDog 에 trace 자동 연결
  • L167-L189: ## 패턴 3: Scope drift 알림 (200 응답)
  • L190-L210: ## 패턴 4: 사용자에게 노출할 1줄 메시지
  • L211-L218: ## 운영 체크리스트
  • L219-L223: ## 보안 주의
  • L224-L229: ## 참고

응답 헤더 / Body 시그널 활용

logi 는 RFC 6749 표준 응답에 더해 RP 서버가 콘솔에 들어오지 않고도 디버깅·모니터링에 쓸 수 있는 신호들을 동봉합니다. 이 문서는 그 신호들을 미들웨어 단에서 잡아 서버 로그· 관측 시스템에 자동으로 흘려보내는 패턴을 보여줍니다.

시그널 카탈로그

Body 필드 (4xx 응답)

필드항상 존재용도
error기계 판독 코드 (RFC 6749 §5.2)
error_description1줄 설명
error_uri이 코드의 docs 앵커
request_id응답마다콘솔 request_logs 와 동일 키

위 필드는 OAuth/RFC 오류 응답 body 의 형태입니다. rack-attack 으로 throttle 된 429 는 별도 형태로, body 에 {"error":"rate_limited"} 만 담기며 error_description/error_uri/request_id 는 포함하지 않습니다 (아래 Rate-limit 시그널 참고).

응답 헤더 (OAuth 응답)

아래 X-Logi-* 헤더는 OAuth/OIDC 엔드포인트의 응답에 상황별로 동봉됩니다. rack-attack 으로 throttle 된 429 응답은 별도이며 X-Logi-* 헤더를 포함하지 않습니다 (아래 Rate-limit 시그널 참고).

헤더언제용도
X-Logi-Request-IdOAuth/RFC 4xx 오류 응답 시 항상 (rack-attack 429 제외)body 가 비어있는 경우(HEAD, error rendering 실패)에도 trace
X-Logi-Console-UrlOAuth/RFC 4xx 오류 + RP 인증 성공 시콘솔 request_logs 의 해당 요청으로 직링크
X-Logi-Scope-Drifttoken 200 응답 + 최근 7일 내 drift 이력이 있을 때등록 안 된 scope 감지 — 즉시 알림용. 기본 block 정책에선 drift 요청 자체가 invalid_scope 로 거절되므로, 이 헤더는 log_only/alert 로 명시 설정된 앱의 진행 중 drift 또는 거절·기록된 drift 이력의 echo 입니다

Rate-limit 시그널 (429 응답)

요청이 rate limit 을 초과하면 logi 는 HTTP 429 Too Many Requests 와 함께 다음을 반환합니다.

시그널위치용도
{"error":"rate_limited"}body기계 판독 코드 — 4xx 로깅 패턴(아래 패턴 1)이 그대로 잡습니다
Retry-After응답 헤더재시도까지 대기 시간(초). 일부 엔드포인트에 포함되며, 백오프 간격으로 사용

rack-attack 으로 throttle 된 429 응답은 위 두 시그널(JSON body + Retry-After)만 동봉합니다 — 이 경로에서는 X-Logi-* 추가 헤더가 붙지 않으니, 분기 키는 status 429 + error: "rate_limited" 로 잡으세요. 클라이언트는 429 수신 시 Retry-After 가 있으면 그 값을, 없으면 지수 백오프(1 → 2 → 4 → 8초)를 적용해 재시도하세요. 엔드포인트별 한도와 백오프 권장사항은 Rate Limits 참고.

패턴 1: 모든 4xx 응답을 자동 로깅

Node.js / Express + axios

js
import axios from 'axios'

const logiClient = axios.create({ baseURL: 'https://api.1pass.dev' })

logiClient.interceptors.response.use(
  (res) => res,
  (err) => {
    if (err.response?.status >= 400 && err.response?.status < 500) {
      const { data, headers, status } = err.response
      console.warn('[logi] 4xx', {
        status,
        error: data?.error,
        error_description: data?.error_description,
        error_uri: data?.error_uri,
        request_id: data?.request_id || headers['x-logi-request-id'],
        console_url: headers['x-logi-console-url'], // 운영자가 즉시 클릭
      })
    }
    return Promise.reject(err)
  }
)

Rails + Faraday

ruby
class LogiResponseLogger < Faraday::Middleware
  def on_complete(env)
    return unless env.status >= 400 && env.status < 500

    body = JSON.parse(env.body) rescue {}
    Rails.logger.warn(
      "[logi] 4xx",
      status: env.status,
      error: body["error"],
      error_description: body["error_description"],
      error_uri: body["error_uri"],
      request_id: body["request_id"] || env.response_headers["x-logi-request-id"],
      console_url: env.response_headers["x-logi-console-url"]
    )
  end
end

Python + requests

python
import logging, requests

def log_logi_4xx(resp: requests.Response):
    if not (400 <= resp.status_code < 500):
        return
    body = resp.json() if "json" in resp.headers.get("content-type", "") else {}
    logging.warning(
        "[logi] 4xx",
        extra={
            "status": resp.status_code,
            "error": body.get("error"),
            "error_description": body.get("error_description"),
            "error_uri": body.get("error_uri"),
            "request_id": body.get("request_id") or resp.headers.get("X-Logi-Request-Id"),
            "console_url": resp.headers.get("X-Logi-Console-Url"),
        },
    )

패턴 2: Sentry / DataDog 에 trace 자동 연결

request_id 를 RP 의 distributed tracing key 와 묶으면, RP 자기 dashboard 에서 "이 사용자의 결제 실패 → logi 토큰 발급 실패" 까지 한 트레이스로 따라갈 수 있습니다.

js
import * as Sentry from '@sentry/node'

logiClient.interceptors.response.use(undefined, (err) => {
  if (err.response?.status >= 400) {
    const reqId = err.response.data?.request_id ||
                  err.response.headers['x-logi-request-id']
    Sentry.withScope((scope) => {
      scope.setTag('logi.request_id', reqId)
      scope.setTag('logi.error', err.response.data?.error)
      scope.setExtra('logi.console_url', err.response.headers['x-logi-console-url'])
      Sentry.captureException(err)
    })
  }
  return Promise.reject(err)
})

이렇게 하면 Sentry 이슈 페이지에서 logi 콘솔로 1-click 점프 가능.

패턴 3: Scope drift 알림 (200 응답)

scope drift 의 기본 정책은 block 입니다 — 등록 안 된 scope 가 포함된 요청은 invalid_scope 로 거절됩니다 (Scope drift 정책). X-Logi-Scope-Drift 헤더는 두 경우에 정상 token 응답에 동봉됩니다: 앱이 log_only/alert 정책으로 명시 설정되어 drift 가 drop 후 진행 중일 때, 또는 최근 7일 내 기록된 drift 이력(block 거절 포함)이 echo 될 때 — 누락 scope 가 콤마로 들어옵니다.

js
logiClient.interceptors.response.use((res) => {
  const drift = res.headers['x-logi-scope-drift']
  if (drift) {
    console.warn('[logi] scope drift detected:', drift.split(','),
                 '— 콘솔에서 등록 scope 를 업데이트하세요.')
  }
  return res
})

drift 이력이 살아있는 7일 동안은 token 응답마다 동봉되므로, RP 가 scope 변경을 잊고 배포해도 서버 로그에 자동으로 알람이 뜹니다.

패턴 4: 사용자에게 노출할 1줄 메시지

error_description 은 한국어/영어 혼용일 수 있고 기술적입니다. 사용자에게 보여줄 때는 error 코드 → 사용자 메시지 매핑 테이블을 RP 가 만들어두는 것을 권장:

ts
const USER_FACING: Record<string, string> = {
  invalid_grant: '로그인이 만료됐어요. 다시 시도해주세요.',
  access_denied: '로그인이 취소됐어요.',
  rate_limited: '요청이 너무 많아요. 잠시 후 다시 시도해주세요.',
  invalid_scope: '앱 설정 문제로 일시적으로 로그인할 수 없어요.',
}

function userFacingMessage(error: string) {
  return USER_FACING[error] ?? '잠시 후 다시 시도해주세요.'
}

기술 정보(error, request_id, console_url)는 서버 로그로, 사용자에겐 친절한 1줄만 — 이렇게 분리해야 사용자가 stack trace 같은 걸 보지 않습니다.

운영 체크리스트

  • [ ] RP 서버가 4xx 시 error, error_description, request_id 셋을 모두 INFO 레벨로 기록한다
  • [ ] X-Logi-Console-Url 헤더를 alerting 메시지에 포함시킨다 (운영자 1-click 점프)
  • [ ] X-Logi-Scope-Drift 헤더가 뜨면 RP 의 monitoring 시스템에 medium-priority 알람을 발사한다
  • [ ] request_id 를 RP 의 trace ID 와 동일 필드에 매핑하거나, 적어도 같은 line 에 출력한다
  • [ ] 사용자 노출 메시지는 error 코드 매핑 테이블을 거친 한국어로만 제한한다

보안 주의

응답 헤더와 body 의 추가 시그널은 모두 PII 가 아닙니다 — request_id, error code, console URL 만 포함됩니다. 사용자 식별자(email, sub), 토큰 값, secret 은 절대 포함되지 않으므로 일반 로그에 남겨도 안전합니다.

참고

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