Troubleshooting
OAuth 연동에서 가장 흔한 실패 패턴은 환경변수 누락입니다. 코드는 그대로지만 prod 빌드에 env가 안 주입돼서 IdP URL이 localhost로 떨어지는 케이스가 압도적으로 많습니다. 이 페이지는 그 1순위부터 점검합니다.
RP 측 필수 환경변수 체크리스트
OAuth 클라이언트(=RP, Relying Party) 앱이 logi에 붙으려면 다음 4개가 prod에 모두 주입돼야 합니다.
| 변수 | 값 예시 | 빠지면 |
|---|---|---|
LOGI_API_URL | https://api.1pass.dev | localhost:3000으로 fallback → 연결 실패 |
LOGI_CLIENT_ID | logi_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | client_id missing 400 |
LOGI_CLIENT_SECRET | logi_secret_… | /oauth/token 단계 401 |
LOGI_SCOPES (선택) | openid profile:basic email | 코드 기본값 사용 (보통 OK) |
⚠️ 변수명은 프로젝트 컨벤션을 따르세요
이 문서는 LOGI_* 네이밍을 권장합니다. 다른 컨벤션(ONE_PASS_*, IDP_* 등)을 쓰는 코드베이스라면 4개 모두 그 prefix로 통일하세요. 하나라도 prefix가 다르면 코드가 ENV.fetch에서 못 찾고 default fallback 됩니다.
자가 점검: 1분 안에 원인 찾기
# 1. RP에서 logi 서버 도달성 확인
curl -I "$LOGI_API_URL/.well-known/openid-configuration"
# → 200 OK 가 떠야 함. 안 뜨면 LOGI_API_URL 잘못됨.
# 2. client_id 가 logi에 등록돼 있는지 확인 (CLI)
logi apps verify $LOGI_CLIENT_ID --redirect-uri "$YOUR_CALLBACK_URL"
# 3. 직접 authorize URL 만들어서 브라우저로 열어보기
echo "$LOGI_API_URL/oauth/authorize?response_type=code&client_id=$LOGI_CLIENT_ID&redirect_uri=$YOUR_CALLBACK_URL&scope=openid&state=test&code_challenge=test&code_challenge_method=plain"
# → consent 화면이 뜨면 RP→IdP 경로 정상.
# → "Invalid redirect_uri" 가 뜨면 logi DB에 redirect_uri 미등록 또는 sandbox tier.흔한 실패 시나리오
❌ 시나리오 1: 로컬은 되는데 prod에서 "리다이렉트가 안 됨" / 빈 화면
증상: 로그인 버튼 클릭 → 잠시 로딩 → ERR_CONNECTION_REFUSED 또는 빈 페이지.
원인: LOGI_API_URL 환경변수가 prod에 안 주입됨. 코드 fallback이 http://localhost:3000이라 브라우저가 사용자 PC의 3000번 포트로 요청.
확인:
# Render 예시
curl -s "https://api.render.com/v1/services/$SERVICE_ID/env-vars" \
-H "Authorization: Bearer $RENDER_API_KEY" | jq '.[] | select(.envVar.key | startswith("LOGI"))'수정: 배포 플랫폼(Vercel/Render/Fly/Railway/...)의 env 설정에 LOGI_API_URL=https://api.1pass.dev 추가 → redeploy.
❌ 시나리오 2: Invalid redirect_uri 에러
증상: logi authorize 페이지 진입은 되는데 error=invalid_redirect_uri 또는 빨간 에러 화면.
원인 A — 등록 안 됨: prod callback URL이 logi DB에 등록 안 됨. 원인 B — sandbox tier: 앱 tier가 sandbox면 localhost/*.staging.*/*.test.*/*.localhost 만 허용. onrender.com/vercel.app 같은 prod 도메인은 차단. 원인 C — 🌟 커스텀 도메인 누락 (가장 흔함): 배포 플랫폼 default URL (*.onrender.com / *.vercel.app / *.fly.dev) 만 등록하고 실제 prod 도메인 (www.example.com) 을 빠뜨린 경우. RP 코드는 자기 도메인으로 콜백을 만들어 보내는데, logi DB 에는 onrender URL 만 있어서 redirect_uri not registered 거부. redirect_uri 매칭은 scheme + host + path 가 정확히 일치 해야 함 — substring/wildcard 매칭 없음.
확인:
logi apps show $CLIENT_ID
# tier: sandbox ← 이거면 production 으로 승급 필요
# redirect_uris: [...] ← prod URL 들어있는지 확인수정:
# redirect_uri 추가 (변종 도메인 모두 등록 권장)
logi apps add-redirect $CLIENT_ID "https://www.example.com/auth/callback"
logi apps add-redirect $CLIENT_ID "https://example.com/auth/callback" # apex 도 등록
logi apps add-redirect $CLIENT_ID "https://yourapp.onrender.com/auth/callback" # 백업 (debug 용)
# tier 승급 (개발자 포털 → Submit for review → 승인)
# 자세한 절차: https://docs.1pass.dev/guide/security#promoting-to-production🌟 권장: 커스텀 도메인 + 플랫폼 URL 모두 등록
배포 플랫폼 default URL (*.onrender.com 등) 도 함께 등록해두면:
- 커스텀 도메인 DNS/SSL 사고 시 backup 경로
- staging/preview 배포 디버깅
- 도메인 마이그레이션 시 cutover 윈도우 확보
redirect_uris 는 배열이라 여러 개 등록 가능합니다 (개수 제한은 plan 기준 — free 5개, pro 20개).
왜 sandbox tier 가 prod 도메인을 차단하나요?
개발 단계 앱이 prod 사용자 데이터에 접근하지 못하도록 막는 안전장치입니다 (Stripe pk_test_… 와 동일 사상). 실수로 test 키로 prod 트래픽을 받는 사고를 원천 차단합니다. 자세한 보안 모델: Security · Sandbox vs Production.
❌ 시나리오 3: Callback 까지 오는데 /oauth/token 에서 401
증상: code 까지 받았는데 token 교환에서 invalid_client 401.
원인 후보:
LOGI_CLIENT_SECRETprod env 미주입 → fallback 또는 빈 값- client_secret 회전됐는데 RP env 업데이트 누락
redirect_urimismatch — authorize 때와 token 때의 redirect_uri 가 정확히 일치해야 함 (스키마/포트/trailing slash 포함)
수정:
# secret 회전 후 새 값 주입
logi apps rotate-secret $CLIENT_ID
# → 출력된 client_secret 을 RP env 에 즉시 복사❌ 시나리오 4: Mac/iOS 데스크톱에서 SSO 버튼 클릭 시 native 앱 안 열림 (브라우저로만 열림)
증상: RP 로그인 페이지에서 "logi 로 로그인" 클릭 → Safari/Chrome에서 api.1pass.dev/session/new 가 그대로 보임. logi Mac 앱이 설치돼있는데도 안 열림.
원인: RP가 server-side 302 redirect로 OAuth flow를 시작하는 패턴. macOS Universal Link는 사용자가 직접 클릭한 링크의 도메인 만 AASA 매칭하고, redirect chain은 user-gesture를 끊어서 무시합니다. iOS 14+ 는 redirect 후에도 매칭하지만 macOS는 더 엄격.
해결: RP 로그인 페이지 렌더 시점에 server에서 api.1pass.dev/oauth/authorize?… URL을 미리 생성하여 <a href> 에 직접 박아둡니다. 클릭이 logi 도메인을 향해 가니까 macOS가 AASA 매칭 → 네이티브 앱 open.
# Rails 예시 — Web::SessionsController#new
def new
state = SecureRandom.urlsafe_base64(32)
verifier, challenge = OnePassSsoService.generate_pkce
session[:one_pass_state] = state
session[:one_pass_code_verifier] = verifier
render inertia: "Auth/Login", props: {
one_pass_authorize_url: OnePassSsoService.authorize_url(
redirect_uri: auth_1pass_callback_url,
state: state,
code_challenge: challenge
)
}
end<a href={one_pass_authorize_url} data-turbo="false" rel="external">logi 로 로그인</a>PKCE verifier/state 는 server session에 그대로 보존 → 보안 영향 없음. callback flow 는 기존과 동일.
⚠️ 주소창에 URL을 paste해서 enter는 macOS Universal Link를 트리거하지 않습니다 (클릭 링크만 트리거). 검증할 때 반드시 실제
<a>태그 클릭으로 테스트.
❌ 시나리오 5: native 앱은 열리는데 동의 화면에 "불러오기 실패"
증상: Universal Link로 logi 앱이 정상 open → 동의 sheet 표시 → "불러오기 실패" / 빈 화면 / 즉시 dismiss.
원인: Cold start race condition. iOS/Mac 앱이 Universal Link 받으면 OAuthConsentView가 즉시 /api/v1/oauth/authorize/preview 를 호출하는데, SessionStore.bootstrap()이 Keychain에서 PAK를 꺼내 APIClient 에 주입하기 전에 호출이 나가버려서 401 unauthenticated 반환 → 동의 화면이 "불러오기 실패" 상태로 멈춤.
해결: 동의 sheet 표시를 session.state == .signedIn 이후로 지연 (LogiApp.swift / LogiMacApp.swift):
private var oauthSheetBinding: Binding<IdentifiableRequest?> {
Binding(
get: {
guard case .signedIn = session.state else { return nil } // ← race 차단
return IncomingOAuthRouter.shared.pending.map(IdentifiableRequest.init)
},
set: { value in
if value == nil { IncomingOAuthRouter.shared.clear() }
}
)
}이렇게 하면 cold start 시: Universal Link 수신 → pending 보관 → bootstrap() 완료 → signedIn 전환 → 그 시점에 sheet 표시 (PAK 주입 끝난 상태).
❌ 시나리오 6: Localhost 에서도 안 됨
원인: logi 서버 미실행 또는 다른 포트.
확인:
curl http://localhost:3000/up # logi 서버 헬스체크
# OK → logi 동작 중
# Connection refused → logi 서버 안 돌아감수정: logi 디렉토리에서 bin/rails server 또는 cd server && bin/dev.
❌ 시나리오 7: TestFlight 빌드 설치했는데 Universal Link 가 안 열림
증상: TestFlight 로 logi 앱 설치 + 한 번 실행 + Safari 에서 RP 사이트 → 1pass 버튼 탭 → logi 앱이 안 열리고 api.1pass.dev/session/new 가 그대로 보임. 시나리오 4 (server-side 302) 이슈는 이미 해결됐고 RP 가 <a href="api.1pass.dev/oauth/authorize?..."> 로 직접 박는데도 안 됨.
원인: entitlement 에 applinks:api.1pass.dev?mode=developer 가 들어간 채 Apple Distribution profile 로 빌드/제출됨. Apple 공식 문서 + WWDC19 session 717:
"Apps signed for distribution on the App Store or TestFlight or Mac apps that have been signed and notarized cannot be used with this alternate mode."
즉 Distribution-signed 빌드에서 ?mode=developer 토큰은 silently ignored. 그 결과:
- AASA association 이 OS 측에 등록조차 안 됨
swcd가 해당 도메인을 모르는 상태- Universal Link 매칭 실패 → 브라우저로 fallback
build 23 사고의 정확한 증상이 이것 (2026-04).
확인:
# (macOS 디바이스에서) 앱이 설치된 상태로
sudo swcutil show | grep -A 8 "1pass.dev"
# 정상이면: Status: approved/approved
# 비정상이면: 도메인 자체가 안 보이거나 "denied" 상태수정:
ios/Sources/logi.entitlements와ios/project.yml의 entitlement 를 plain 으로:diff- applinks:api.1pass.dev?mode=developer + applinks:api.1pass.dev- mac 도 동일하게 (
mac/Sources/LogiMac.entitlements+mac/project.yml) - 빌드번호 +1 (23 → 24+) 후 새 TestFlight 업로드:bash
cd ios && make testflight cd mac && make testflight - 새 빌드 설치 후 앱 한 번 실행 → Safari 에서 RP 사이트 → 1pass 버튼 탭 검증
?mode=developer 가 정말 필요한 시점은 로컬 Xcode debug 빌드뿐. make dev-mode-on / make dev-mode-off 토글로 관리하고 절대 commit 금지. 자세한 내용은 RELEASE_CHECKLIST.md 참고.
🚫 검증 시 주의: 주소창에
https://api.1pass.dev/oauth/authorize?...직접 paste → 동일 도메인 출처라 Universal Link 트리거 자체가 무시됨 (Apple 의도 동작). 반드시 다른 도메인의 사이트에서<a>클릭으로 검증.
🚫 Chrome / Firefox / Brave / Safari 시크릿 모드는 Universal Link 트리거가 일관되지 않거나 아예 안 됨. Safari 정상 모드 에서만 검증.
배포 플랫폼별 env 설정 가이드
Render
# 단일 변수 추가/수정 (collection PUT 은 절대 사용 금지 — 전체 교체됨)
curl -X PUT "https://api.render.com/v1/services/$SERVICE_ID/env-vars/LOGI_API_URL" \
-H "Authorization: Bearer $RENDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"value": "https://api.1pass.dev"}'
# 변경 후 redeploy
curl -X POST "https://api.render.com/v1/services/$SERVICE_ID/deploys" \
-H "Authorization: Bearer $RENDER_API_KEY" \
-d '{"clearCache": "do_not_clear"}'Vercel
vercel env add LOGI_API_URL production
# → 프롬프트에서 https://api.1pass.dev 입력
vercel --prod # redeployFly.io
fly secrets set LOGI_API_URL=https://api.1pass.dev
# 자동 redeploy디버그 모드
RP 코드에서 IdP 응답을 자세히 보고 싶을 때:
# Rails 예시
Rails.logger.info "[logi] authorize_url=#{url}"
Rails.logger.info "[logi] token_response=#{tokens.except('access_token', 'refresh_token').inspect}"logi 서버 측 로그는 logi token inspect <access_token> 으로 디코드해서 확인 가능합니다.
그래도 안 되면
- GitHub Issues — 재현 가능한 최소 예제 첨부
- 로그에
client_id,redirect_uri, HTTP status code 만 포함하면 90%는 빠르게 진단됩니다 (access_token/secret 은 절대 첨부 금지).