Skip to content

Web SSO (데스크톱 Apple/Google 로그인)

일반 RP 통합은 별도 트랙

두 가지 진입점

호스트사용자destination
api.1pass.dev/session/new일반 사용자/oauth/authorize?... (RP consent)
start.1pass.dev/console/login개발자/developer (cross-host handoff)

흐름

사용자 브라우저

      ▼  ① 클릭 "Apple/Google로 계속"
api.1pass.dev/auth/{apple,google}/web/start?return_to=<path>
      │  ② OauthWebState row 발급 (state, nonce, return_to, 10min TTL)
      │  ③ 302 to Apple/Google authorize endpoint

appleid.apple.com  /  accounts.google.com
      │  ④ 사용자 로그인 + consent
      ▼  ⑤ 콜백
api.1pass.dev/auth/{apple,google}/web/callback
      │  ⑥ state 단발 consume (provider-scoped)
      │  ⑦ code → id_token 교환
      │  ⑧ id_token JWKs 검증 (alg, iss, aud, exp, nonce, sub, email_verified)
      │  ⑨ WebSsoUserResolver: find by sub → email merge → create
      │  ⑩ start_new_session_for → Session 발급

        ┌─────────── return_to 가 /oauth/authorize 인가?
        │  YES                                    NO (콘솔 path)
        ▼                                          ▼
api.1pass.dev 쿠키 세팅                  SessionHandoffToken 발급 (30s TTL)
return_to 로 redirect                    api.1pass.dev 쿠키 삭제
                                          start.1pass.dev/session/handoff?token=...

                                          start.1pass.dev 가 토큰 consume
                                          자기 호스트 쿠키 발급
                                          return_to 로 redirect

라우트

컨트롤러라우트
Web::GoogleSsoControllerGET /auth/google/web/start, GET /auth/google/web/callback
Web::AppleSsoControllerGET /auth/apple/web/start, POST /auth/apple/web/callback (form_post, CSRF skip)
SessionHandoffsControllerGET /session/handoff (console-host-constrained)

콘솔 설정

Apple Services ID

  1. Identifiers → Services IDs → +
  2. Description: logi Web SSO
  3. Identifier: dev.1pass.web (= APPLE_WEB_SERVICES_ID)
  4. Sign in with Apple → Configure
    • Primary App ID: com.dcodelabs.logi (native bundle 과 같은 2TSQS58S44 키가 web client_secret JWT 도 서명)
    • Domains: api.1pass.dev
    • Return URLs: https://api.1pass.dev/auth/apple/web/callback

Google OAuth Web Client

  1. APIs & Services → Credentials → + CREATE CREDENTIALS → OAuth client ID
  2. Application type: Web application
  3. Name: logi Web SSO
  4. Authorized JavaScript origins: https://api.1pass.dev
  5. Authorized redirect URIs: https://api.1pass.dev/auth/google/web/callback
  6. OAuth Consent Screen → PUBLISH APP (Testing 모드면 등록된 test users 만 가능)

ENV (Render logi-server)

APPLE_WEB_SERVICES_ID=dev.1pass.web

# Apple SIWA 키 (native + web 공유)
APPLE_TEAM_ID=74PTNNLD4P
APPLE_KEY_ID=2TSQS58S44
APPLE_PRIVATE_KEY=<.p8 PEM 내용 — 또는 APPLE_PRIVATE_KEY_PATH 로 마운트>

GOOGLE_WEB_CLIENT_ID=<공개 Client ID>
GOOGLE_WEB_CLIENT_SECRET=<Client Secret>

⚠️ Render env 업데이트는 항상 개별 PUT (Collection PUT 금지). MCP update_environment_variables 는 default replace=false (merge) 로 안전.

보안 invariants

  • State 는 server-side OauthWebState row, single-use, provider-scoped, 10min TTL.
  • Return_to 는 /start 에서 검증 후 DB 저장, 콜백은 row 값만 사용 (param 신뢰 X).
  • safe_return_to? 는 path-exact 매치 (/oauth/authorize, /console, /console/... 등).
  • ID token: alg whitelist (ES256, RS256), kid 필수, iss/aud exact, exp+iat skew 60s, nonce, sub non-empty, Google azp (있을 때) exact, Google email_verified=true 강제.
  • Apple nonce 는 direct equality (native 의 SHA256 hashing 과 다름).
  • JWKs unknown-kid forced refresh (key rotation outage 방지).
  • /start rate limit 30/min/IP.
  • 콜백 후 reset_sessionstart_new_session_for (session fixation 방어).
  • Apple user form field: 2KB raw cap + 128B per name part.
  • Pending-purge / locked / suspended account 차단 (AccountPendingPurge, AccountLockedOrSuspended, ProviderSubConflict).
  • 세션 쿠키 domain: 은 host-only (embed.1pass.dev 격리 유지).
  • Handoff endpoint return_to 화이트리스트: /console, /developer, /demo 만 허용 (/oauth/authorize 거부).

운영 절차

Google Client Secret 로테이션

  1. Google Cloud Console → Credentials → 해당 OAuth Client → Add Secret
  2. Render env GOOGLE_WEB_CLIENT_SECRET 업데이트 (merge)
  3. Deploy + 정상 로그인 확인
  4. 구 secret Disable

Apple .p8 로테이션

  1. Apple Developer → Keys → 새 키 생성 (SIWA capability)
  2. Render env: APPLE_KEY_ID + APPLE_PRIVATE_KEY 업데이트
  3. Deploy + 정상 로그인 확인
  4. 구 키 revoke

Pending-purge 사용자

소프트삭제 grace 기간 내 사용자가 web SSO 재가입 시도하면 AccountPendingPurge 차단. 모바일 앱 복구 흐름 (POST /api/v1/account_recoveriesconsume) 후 재시도.

트러블슈팅

증상원인해결
콘솔 SSO 후 다시 로그인 페이지로SessionHandoffToken 미발급 or 만료 (30s)Render 로그에서 [session_handoff] failure: grep
Google access_deniedConsent screen Testing 모드OAuth consent screen → PUBLISH APP
Apple invalid_clientclient_id 에 Bundle ID 들어감APPLE_WEB_SERVICES_ID=dev.1pass.web 확인
jwks: unknown kid after refresh키 로테이션 또는 token 위조Rails.cacheauth/jwks_cache/{apple_web,google} 강제 무효화 후 재시도
state mismatch 빈도 증가10min TTL 초과 또는 multi-tabTTL 확인. provider-scoped 라 multi-tab 안전

알려진 한계

  • 모바일 UA 는 SSO 카드 표시 안 함 — native 앱 흐름 (logi:// + Universal Links) 가 canonical.
  • 콘솔 SSO 직후 passkey 강제 안 함 (advisory).
  • Apple private relay 의 cross-provider merge 불가 (native device-link 로만 통합).

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