테마
Refresh Token 정책 (Apple / Google)
logi 는 두 가지 종류의 refresh token 을 다룹니다:
- logi → RP — logi 가 RP 에게 발급하는 OAuth access/refresh token. RP 통합에서 "refresh token" 이라 하면 보통 이것.
- Apple/Google → logi — 업스트림 SSO 공급자가 logi 서버에 발급하는 refresh token. RP 가 직접 보지 않으나, 사용자 경험에 영향을 줍니다.
이 페이지는 후자(업스트림) 의 정책과 그것이 RP 측 사용자 경험에 미치는 영향을 정리합니다.
Apple refresh token
정책
- 만료 없음 — Apple 의 refresh token 은 명시적 만료가 없습니다. 한 번 발급되면 사용자가 [iPhone 설정 → Apple ID → 로그인된 앱]에서 Apple 로그인 사용을 해제하거나, logi 서버가
/auth/revoke를 호출할 때까지 유효 client_id바인딩 — Apple 은 refresh token 을 발급 시점의 정확한client_id에 바인딩합니다. 다른client_id로/auth/revoke를 호출하면 silent 4xx- 단일 토큰 in-flight — Apple 의 authorization code 는 ~5분 단일 사용. 재발급은 사용자가 다시 Sign in with Apple 을 누를 때만 발생
logi 의 처리
테이블 컬럼 (마이그레이션 20260504130000_add_apple_refresh_token_to_users.rb + 20260504143000_add_apple_refresh_token_client_id_to_users.rb):
| 컬럼 | 용도 |
|---|---|
users.apple_refresh_token | 평문 refresh_token (서버 내부 보관) |
users.apple_refresh_token_obtained_at | 교환 성공 시각 |
users.apple_refresh_token_client_id | 발급 시점의 client_id (revoke 시 동일 값 사용 필수) |
흐름:
- 사용자가 Sign in with Apple 수행 → identity_token + authorization_code 를 logi 가 수신
ExchangeAppleAuthorizationCodeJob가 비동기로 Apple/auth/token호출 → refresh_token 획득 → 위 컬럼에 저장- 사용자가 계정 삭제 시
RevokeAppleGrantJob가 저장된 refresh_token 으로 Apple/auth/revoke호출
Refresh token 은 access token 갱신용이 아님
logi 가 저장하는 Apple refresh token 은 오직 revoke 용입니다. logi 가 사용자에게 발급하는 access token 의 갱신은 Apple 의 refresh token 과 무관하게 logi 자체 발급 refresh token 으로 수행합니다.
Google refresh token
정책
- 60일 비활성 만료 — Google 은 60일간 미사용 refresh token 을 자동 폐기
- 6개월 미사용 시 강제 재동의 — 비활성 계정의 grant 는 만료될 수 있음
- Scope 변경 시 재발급 필요 — 기존 토큰의 scope 와 다른 scope 를 요청하면 새 동의 화면 + 새 refresh token
logi 의 처리
logi 는 Google refresh token 을 저장하지 않습니다.
Auth::GoogleSessionsController#create 는 클라이언트(iOS/Android/Web)가 자체적으로 Google Sign-In SDK 로 받아온 id_token 만 받아 검증합니다 (identity_token + raw_nonce 파라미터). 즉:
- logi 서버는 Google
/oauth2/v4/token을 호출할 일이 없음 - Google refresh token 은 클라이언트 측에만 존재 (또는 클라이언트가 저장하지 않으면 아예 부재)
- 컬럼:
users.google_sub,users.google_email,users.google_linked_at— 토큰 컬럼 없음
결과: Google SSO 가 만료되거나 사용자가 [Google 계정 → 보안 → 제3자 앱]에서 logi 를 해제해도 logi 가 즉시 감지할 수 없습니다. 다음 로그인 시도에서 클라이언트가 새 id_token 을 받아 올 때 비로소 logi 가 갱신된 상태를 반영.
RP 측 영향
RP 의 통합 코드는 logi 자체 access/refresh token 만 다룹니다. 업스트림 Apple/Google 토큰은 RP 에서 보이지 않습니다. RP 가 알아야 할 단 한 가지:
RP 가 해야 할 일
logi access token 이 401 을 반환하면 사용자에게 재로그인을 유도. 이는 logi 자체 토큰 만료일 수도 있고, 업스트림 SSO 가 끊겼을 수도 있습니다 (revoke / 비활성 만료 / 강제 재동의). RP 는 두 경우를 구분할 필요 없이 같은 UX 로 처리.
logi 자체 refresh token 의 회전 정책 (재사용 감지 → chain revoke) 은 Security Best Practices §토큰 유출 자동 무력화 참조.
운영 함정
Google: 60일 비활성 사용자
가장 자주 만나는 케이스. 사용자 시나리오:
- 사용자가 Google SSO 로 가입 후 60일 이상 앱을 사용하지 않음
- Google 이 refresh token 자동 폐기 (사용자에게 별다른 알림 없음)
- 사용자가 앱 재실행 → Google Sign-In SDK 가 silent sign-in 실패 → 동의 화면 다시 표시
- 사용자는 "왜 또 동의해?" 라고 느낌
RP 측 권장: account-merge 흐름 이 정상 동작하면 새 동의 후에도 동일한 canonical_sub 가 반환되므로 사용자 데이터는 손실 없음. 단, UX 카피로 "보안 정책에 따라 다시 동의가 필요합니다" 안내 권장.
Apple: client_id 분기
logi 는 동일 사용자가 여러 Apple client_id (예: com.dcodelabs.logi, com.dcodelabs.logi.mac, com.dcodelabs.logi.Clip) 로 가입할 수 있습니다. 각 client_id 별로 별도의 refresh token 이 발급되지만, logi 의 users.apple_refresh_token 컬럼은 1개만 저장.
영향:
- 사용자가 두 client 로 모두 로그인하면, 가장 최근 교환 결과가 덮어씁니다 (
ExchangeAppleAuthorizationCodeJob는apple_refresh_token이 이미 존재하면 skip — 다만 이 정책은 코드에서 확인 필요) - 계정 삭제 시 logi 는
apple_refresh_token_client_id에 저장된 1개 client 에 대해서만/auth/revoke를 호출. 다른 client 의 grant 는 사용자가 직접 iPhone 설정에서 해제해야 함
Apple: revoke 4xx 무시
AppleRevocation 는 best-effort 입니다. Apple /auth/revoke 가 4xx (이미 만료, client_id mismatch 등) 또는 5xx 를 반환해도 logi 의 계정 삭제는 진행됩니다. 백업 경로:
- 사용자가 iPhone [설정 → Apple ID → 로그인된 앱] 에서 수동 해제
- Apple S2S consent-revoked notification (
Auth::AppleNotificationsController) 가 logi 로 들어오면 logi 가 해당 사용자를 정리
RP 가 추가로 할 일은 없습니다.
요약
| 항목 | Apple | |
|---|---|---|
| logi 가 refresh token 저장? | ✅ revoke 전용 | ❌ 저장 안 함 |
| 만료 정책 (업스트림) | 없음 (revoke 시까지) | 60일 비활성 |
| logi 가 자동 감지? | S2S notification 으로 감지 | ❌ 다음 로그인까지 모름 |
| RP 측 대응 | logi access token 401 시 재로그인 유도 | 동일 |
| 사용자 재동의 화면 | revoke 후 첫 로그인 시 | 60일 만료 후 첫 로그인 시 |