테마
Web SSO (데스크톱 Apple/Google 로그인)
일반 RP 통합은 별도 트랙
- 웹 앱 → 🌐 웹 통합 트랙
- 모바일 앱 → 📱 모바일 통합 트랙 이 페이지는 logi 가 Apple/Google 을 upstream IdP 로 쓰는 데스크톱 web SSO 메커니즘입니다.
두 가지 진입점
| 호스트 | 사용자 | 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::GoogleSsoController | GET /auth/google/web/start, GET /auth/google/web/callback |
Web::AppleSsoController | GET /auth/apple/web/start, POST /auth/apple/web/callback (form_post, CSRF skip) |
SessionHandoffsController | GET /session/handoff (console-host-constrained) |
콘솔 설정
Apple Services ID
- Identifiers → Services IDs → +
- Description:
logi Web SSO - Identifier:
dev.1pass.web(=APPLE_WEB_SERVICES_ID) - 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
- Primary App ID:
Google OAuth Web Client
- APIs & Services → Credentials → + CREATE CREDENTIALS → OAuth client ID
- Application type: Web application
- Name:
logi Web SSO - Authorized JavaScript origins:
https://api.1pass.dev - Authorized redirect URIs:
https://api.1pass.dev/auth/google/web/callback - 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
OauthWebStaterow, 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, Googleazp(있을 때) exact, Googleemail_verified=true강제. - Apple nonce 는 direct equality (native 의 SHA256 hashing 과 다름).
- JWKs unknown-kid forced refresh (key rotation outage 방지).
/startrate limit 30/min/IP.- 콜백 후
reset_session→start_new_session_for(session fixation 방어). - Apple
userform 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 로테이션
- Google Cloud Console → Credentials → 해당 OAuth Client → Add Secret
- Render env
GOOGLE_WEB_CLIENT_SECRET업데이트 (merge) - Deploy + 정상 로그인 확인
- 구 secret Disable
Apple .p8 로테이션
- Apple Developer → Keys → 새 키 생성 (SIWA capability)
- Render env:
APPLE_KEY_ID+APPLE_PRIVATE_KEY업데이트 - Deploy + 정상 로그인 확인
- 구 키 revoke
Pending-purge 사용자
소프트삭제 grace 기간 내 사용자가 web SSO 재가입 시도하면 AccountPendingPurge 차단. 모바일 앱 복구 흐름 (POST /api/v1/account_recoveries → consume) 후 재시도.
트러블슈팅
| 증상 | 원인 | 해결 |
|---|---|---|
| 콘솔 SSO 후 다시 로그인 페이지로 | SessionHandoffToken 미발급 or 만료 (30s) | Render 로그에서 [session_handoff] failure: grep |
Google access_denied | Consent screen Testing 모드 | OAuth consent screen → PUBLISH APP |
Apple invalid_client | client_id 에 Bundle ID 들어감 | APPLE_WEB_SERVICES_ID=dev.1pass.web 확인 |
jwks: unknown kid after refresh | 키 로테이션 또는 token 위조 | Rails.cache 의 auth/jwks_cache/{apple_web,google} 강제 무효화 후 재시도 |
state mismatch 빈도 증가 | 10min TTL 초과 또는 multi-tab | TTL 확인. provider-scoped 라 multi-tab 안전 |
알려진 한계
- 모바일 UA 는 SSO 카드 표시 안 함 — native 앱 흐름 (
logi://+ Universal Links) 가 canonical. - 콘솔 SSO 직후 passkey 강제 안 함 (advisory).
- Apple private relay 의 cross-provider merge 불가 (native device-link 로만 통합).