21 KiB
비표준 로그인 승인 화면 공통화 개발안
작성일: 2026-06-23
1. 목적
본 문서는 Baron SSO의 비표준 로그인 방식인 로그인 링크, Magic Link, QR 로그인의 승인 화면을 공통화하기 위한 단계별 개발안을 정의한다.
목표는 다음과 같다.
- 사용자가 승인 전에 "어떤 기기가 로그인되려는지" 쉽게 확인하도록 한다.
- 링크/QR/Magic Link마다 흩어진 승인 UX를 하나의 공통 패턴으로 정리한다.
- 기존 로그인 동작에 영향을 주지 않도록 opt-in 방식으로 개발한다.
- 개발 후 기존 표준 로그인, 링크 로그인, QR 로그인, OIDC 흐름에 영향이 없는지 반드시 확인한다.
2. 현재 상태 요약
현재 코드 기준 관련 위치는 다음과 같다.
| 영역 | 파일 | 현재 역할 |
|---|---|---|
| UserFront API client | userfront/lib/core/services/auth_proxy_service.dart |
링크, Magic Link, QR init/poll/approve API 호출 |
| QR 승인 화면 | userfront/lib/features/auth/presentation/approve_qr_screen.dart |
QR 스캔 후 자동 승인 처리 |
| 로그인 화면 | userfront/lib/features/auth/presentation/login_screen.dart |
비밀번호, 링크, QR 로그인 UI 및 poll 처리 |
| 라우팅 | userfront/lib/main.dart |
/ql/:ref, /qr/approve 계열 라우트 연결 |
| Backend route | backend/cmd/server/main.go |
/api/v1/auth/qr/approve, /magic-link/verify, /enchanted-link/poll 등록 |
| Backend handler | backend/internal/handler/auth_handler.go |
QR/link pending 상태, Redis 저장, 감사 로그 처리 |
이미 존재하는 재사용 가능 코드도 있다.
| 기존 코드 | 재사용 이유 |
|---|---|
storeQrMeta, loadQrMeta |
QR 요청 기기의 IP/User-Agent 저장 구조가 이미 있다. |
storeLoginApproverMeta, loadLoginApproverMeta |
링크 승인자의 IP/User-Agent/session 정보를 저장하는 구조가 이미 있다. |
writeQrAuditLog, writeLinkAuditLog |
승인 성공 감사 로그가 이미 있다. |
AuthProxyService.approveQrLogin |
QR 승인 API client가 이미 있다. |
AuthProxyService.verifyMagicLink, verifyLoginCode |
링크/Magic Link 승인 API client가 이미 있다. |
3. 개발 원칙
이번 개발은 기존 소스코드 영향도를 최소화하기 위해 아래 원칙을 따른다.
- 기존 API 응답 형식을 즉시 변경하지 않는다.
- 기존 QR 자동 승인 화면을 바로 제거하지 않는다.
- 새 공통 승인 화면은 feature flag가 켜졌을 때만 사용한다.
- Backend에는 조회용 metadata API를 먼저 추가하고, 기존 승인 API는 최대한 유지한다.
- 공통 UI는 기존 API client 위에 얇게 얹고, 성공/실패 동작은 기존 흐름과 동일하게 유지한다.
- 차단 버튼은 1차 개발에서는 UI placeholder 또는 별도 후속 단계로 분리할 수 있다.
- 개발 후에는 기존 자동 승인 흐름과 새 수동 승인 흐름을 모두 테스트한다.
4. 목표 화면
공통 승인 화면은 다음 정보를 표시한다.
새 로그인 요청
요청 서비스: Baron SSO 또는 RP 이름
요청 방식: QR 로그인 / 로그인 링크 / Magic Link
요청 기기: Chrome on Windows
요청 위치/IP: 203.0.113.xxx
요청 시간: 2026-06-23 10:30
이 요청을 승인하면 해당 기기가 로그인됩니다.
본인이 요청한 로그인이 맞습니까?
[내 요청입니다] [내 요청이 아닙니다] [잘 모르겠습니다]
1차 개발에서는 버튼 동작을 다음처럼 나눈다.
| 버튼 | 1차 처리 | 후속 처리 |
|---|---|---|
| 내 요청입니다 | 기존 승인 API 호출 | 유지 |
| 내 요청이 아닙니다 | pending block API가 있으면 호출, 없으면 안전 안내 후 승인하지 않음 | blocked_by_user 상태 저장 구현 |
| 잘 모르겠습니다 | 승인하지 않고 안내 화면 표시 | blocked_uncertain 또는 hold 상태 구현 |
5. Backend 개발안
5.1 새 응답 모델 추가
Backend에 공통 승인 요청 metadata 응답 모델을 추가한다.
권장 위치:
backend/internal/domain/auth_models.go
예시:
type LoginApprovalRequestInfo struct {
PendingRef string `json:"pendingRef"`
ApprovalType string `json:"approvalType"` // qr, link, magic_link
Status string `json:"status"` // pending, approved, success, expired, blocked
RequestService string `json:"requestService,omitempty"`
RequestClientID string `json:"requestClientId,omitempty"`
RequestIPAddress string `json:"requestIpAddress,omitempty"`
RequestUserAgent string `json:"requestUserAgent,omitempty"`
RequestDevice string `json:"requestDevice,omitempty"`
RequestedAt string `json:"requestedAt,omitempty"`
ExpiresAt string `json:"expiresAt,omitempty"`
Message string `json:"message,omitempty"`
}
주의:
requestIpAddress는 전체 IP를 그대로 노출하지 않고 마스킹한다.requestUserAgent는 원문과 표시용을 분리하는 것이 좋다.pendingRef가 유효하지 않아도 사용자 존재 여부를 추론할 수 있는 메시지를 주지 않는다.
5.2 공통 승인 정보 조회 API 추가
새 API를 추가한다.
POST /api/v1/auth/approval/info
요청:
{
"pendingRef": "abc123",
"approvalType": "qr"
}
응답:
{
"pendingRef": "abc123",
"approvalType": "qr",
"status": "pending",
"requestService": "Baron SSO",
"requestClientId": "userfront",
"requestIpAddress": "203.0.113.xxx",
"requestUserAgent": "Chrome on Windows",
"requestDevice": "Desktop browser",
"requestedAt": "2026-06-23T10:30:00+09:00",
"expiresAt": "2026-06-23T10:35:00+09:00"
}
구현 위치:
backend/cmd/server/main.go
backend/internal/handler/auth_handler.go
route 추가:
auth.Post("/approval/info", authHandler.GetLoginApprovalInfo)
처리 방식:
pendingRef,approvalType을 받는다.approvalType=qr이면loadQrMeta(pendingRef)를 우선 사용한다.approvalType=link또는magic_link이면loadLoginMeta(pendingRef)와 기존 Redis 상태를 조회한다.- Redis의
prefixSession + pendingRef상태를 확인한다. - 표시 가능한 정보만 응답한다.
영향도:
- 신규 API 추가만 수행하므로 기존 API 동작에 영향 없음.
- 기존
/qr/approve,/magic-link/verify,/login/code/verify는 변경하지 않는다.
5.3 요청 metadata 저장 보강
QR은 이미 storeQrMeta(pendingRef, c)가 있다.
링크 계열은 init 시점의 요청자 metadata 저장이 부족할 수 있으므로, 별도 공통 metadata 저장 함수를 추가한다.
권장 함수:
func (h *AuthHandler) storeApprovalRequestMeta(pendingRef, approvalType, clientID string, c *fiber.Ctx, ttl time.Duration)
func (h *AuthHandler) loadApprovalRequestMeta(pendingRef string) (approvalRequestMeta, bool)
권장 Redis key:
approval_request_meta:{pendingRef}
1차 적용 범위:
InitQRLogin: 기존storeQrMeta유지, 새 metadata도 병행 저장InitEnchantedLink: pendingRef 생성 직후 새 metadata 저장- Magic Link는 token 자체보다 연결된
pendingRef기준으로 metadata 조회
영향도 최소화 방법:
- 기존
prefixQrMeta는 제거하지 않는다. - 새
approval_request_meta는 조회 실패해도 기존 동작을 막지 않는다. - metadata 저장 실패는 로그인 실패로 처리하지 않는다.
5.4 block API는 2단계로 분리
1차 공통 화면에서 내 요청이 아닙니다 버튼은 승인하지 않는 것만으로도 기존 자동 승인보다 안전하다.
다만 최종적으로는 아래 API가 필요하다.
POST /api/v1/auth/approval/block
요청:
{
"pendingRef": "abc123",
"approvalType": "qr",
"decision": "blocked_by_user"
}
응답:
{
"status": "blocked_by_user"
}
2단계 구현 시 처리:
prefixSession + pendingRef의 status를blocked_by_user로 변경한다.- poll API가
blocked_by_user를 반환하게 한다. - 감사 로그
login.request.blocked_by_user를 남긴다.
1차 개발에서는 이 API를 만들되 feature flag로 UI 연결을 늦춰도 된다.
6. UserFront 개발안
6.1 공통 모델 추가
권장 파일:
userfront/lib/features/auth/domain/login_approval_request.dart
예시 필드:
class LoginApprovalRequest {
final String pendingRef;
final String approvalType;
final String status;
final String? requestService;
final String? requestClientId;
final String? requestIpAddress;
final String? requestUserAgent;
final String? requestDevice;
final DateTime? requestedAt;
final DateTime? expiresAt;
}
영향도:
- 신규 파일 추가이므로 기존 화면 영향 없음.
6.2 AuthProxyService API 추가
권장 파일:
userfront/lib/core/services/auth_proxy_service.dart
추가 함수:
static Future<Map<String, dynamic>> getLoginApprovalInfo({
required String pendingRef,
required String approvalType,
})
요청 대상:
POST /api/v1/auth/approval/info
영향도:
- 기존 함수 수정 없이 새 함수만 추가한다.
- 기존 QR/link 로그인 호출 경로는 그대로 유지한다.
6.3 공통 승인 화면 추가
권장 파일:
userfront/lib/features/auth/presentation/login_approval_screen.dart
props:
class LoginApprovalScreen extends StatefulWidget {
final String pendingRef;
final String approvalType; // qr, link, magic_link
final bool autoApproveFallback;
}
화면 동작:
- 진입 시
AuthProxyService.getLoginApprovalInfo호출 - 요청 정보 표시
내 요청입니다클릭 시 approvalType별 기존 API 호출- 성공 시 기존 성공 화면 또는 dashboard 이동
내 요청이 아닙니다,잘 모르겠습니다는 세션 발급 없이 안내
approvalType별 기존 API 매핑:
| approvalType | 승인 API |
|---|---|
qr |
AuthProxyService.approveQrLogin |
magic_link |
AuthProxyService.verifyMagicLink |
link_code |
AuthProxyService.verifyLoginCode |
주의:
- Magic Link는 token 기반이므로
pendingRef만으로 승인할 수 없는 경우가 있다. - 따라서 Magic Link 공통화는 token verify 화면의 wrapper로 시작하고, 1차에서는 QR부터 적용하는 것이 안전하다.
6.4 QR 승인 화면 점진 전환
현재 ApproveQrScreen은 진입 후 자동 승인한다.
1차 변경안:
기존:
/ql/:ref -> ApproveQrScreen -> 자동 approveQrLogin
신규 feature flag ON:
/ql/:ref -> LoginApprovalScreen(approvalType=qr) -> 사용자가 직접 승인
feature flag OFF:
/ql/:ref -> ApproveQrScreen 유지
권장 feature flag:
USERFRONT_COMMON_APPROVAL_SCREEN=true
또는 Flutter runtime env:
VITE/ENV 성격에 맞는 userfront runtime env key로 추가
영향도 최소화:
- 기존
ApproveQrScreen은 삭제하지 않는다. - 라우트에서 feature flag만 보고 새 화면으로 분기한다.
- 문제가 있으면 flag를 끄고 즉시 기존 자동승인으로 복귀한다.
6.5 링크/Magic Link 적용 순서
링크와 Magic Link는 QR보다 변형이 많다. 따라서 QR 적용 후 안정화되면 다음 순서로 진행한다.
- QR 수동 승인 화면 적용
- Magic Link verify 화면에 요청 정보 표시만 추가
- Enchanted Link 승인 흐름에 공통 요청 정보 표시
내 요청이 아닙니다block API 연결
7. 단계별 개발 순서
Phase 0. 기준 테스트 확보
목표:
- 개발 전 현재 동작이 정상임을 확인한다.
확인 항목:
- 비밀번호 로그인 정상
- 링크 로그인 init/poll 정상
- Magic Link verify 정상
- QR init/poll/approve 정상
- OIDC login_challenge가 있는 로그인 정상
권장 명령:
go test ./backend/internal/handler -run 'Test.*QR|Test.*Link|Test.*LoginCode|Test.*Password'
flutter test
npm run test -- tests/orgfront-auto-login.spec.ts --project=chromium
프로젝트 환경에 따라 실제 명령은 조정한다.
Phase 1. Backend 조회 API만 추가
작업:
LoginApprovalRequestInfo모델 추가GetLoginApprovalInfohandler 추가POST /api/v1/auth/approval/inforoute 추가- QR metadata 조회 테스트 추가
- 잘못된 pendingRef 응답 테스트 추가
영향도:
- 신규 API만 추가되므로 기존 로그인 흐름 영향 없음.
완료 기준:
- 기존 테스트 모두 통과
- 신규 API 테스트 통과
- 기존 QR approve API 응답 변경 없음
Phase 2. Backend metadata 저장 보강
작업:
approval_request_meta:{pendingRef}Redis 저장 함수 추가InitQRLogin에 병행 저장 추가InitEnchantedLink에 병행 저장 추가- 저장 실패가 로그인 실패로 이어지지 않게 처리
영향도:
- Redis key만 추가된다.
- 기존 key 삭제/변경 없음.
완료 기준:
- Redis metadata 미저장 상황에서도 로그인 성공
- QR/link 기존 poll/approve 정상
Phase 3. UserFront 공통 화면 신규 추가
작업:
LoginApprovalRequest모델 추가AuthProxyService.getLoginApprovalInfo추가LoginApprovalScreen신규 추가- QR 승인용 UI 연결 로직 작성
- feature flag 기본값 OFF
영향도:
- 기본값 OFF이므로 배포해도 기존 UX 유지
- 새 화면은 직접 route 또는 테스트에서만 접근
완료 기준:
- feature flag OFF에서 기존 QR 자동 승인 동작 동일
- feature flag ON에서 새 승인 화면 표시
- 승인 버튼 클릭 시 기존
approveQrLogin호출
Phase 4. QR 라우트 opt-in 연결
작업:
userfront/lib/main.dart의 QR approve route에서 feature flag 분기- flag ON이면
LoginApprovalScreen(approvalType: 'qr') - flag OFF이면 기존
ApproveQrScreen
영향도:
- flag OFF 기준 영향 없음.
완료 기준:
- flag OFF e2e: 기존 QR 로그인 성공
- flag ON e2e: 요청 정보 확인 후 수동 승인 성공
- 미로그인 승인 기기는 기존처럼 로그인 유도
Phase 5. 링크/Magic Link 화면 확장
작업:
- Magic Link verify 진입 화면에서 approval info 조회
- 가능한 경우 요청 정보 표시
- 기존 verify API 호출은 그대로 유지
- Enchanted Link code verify에도 같은 UI wrapper 적용 검토
영향도:
- token 기반 링크는 기존 URL 처리와 충돌 가능성이 있어 QR보다 늦게 적용한다.
완료 기준:
- 기존 Magic Link URL이 계속 동작
- verifyOnly=true 흐름이 깨지지 않음
- pendingRef 없는 Magic Link도 기존처럼 처리
Phase 6. block API 연결
작업:
POST /api/v1/auth/approval/block추가- pending 상태
blocked_by_user저장 - QR/link poll에서
blocked_by_user반환 - UserFront에서
내 요청이 아닙니다클릭 시 block 호출 - 감사 로그 추가
영향도:
- poll 응답에 새 code가 추가된다.
- 기존 UserFront가 모르는 code를 받지 않도록, UI 연결 전 poll 처리부터 방어적으로 추가한다.
완료 기준:
- 차단된 pendingRef는 세션 발급 불가
- 원래 요청 브라우저는 차단 메시지 표시
- 감사 로그에 차단 이벤트 기록
8. 영향도 관리 방안
8.1 기존 소스코드 영향 최소화
| 대상 | 영향도 관리 |
|---|---|
| Backend 기존 승인 API | 응답 구조 변경 금지 |
| Backend Redis 기존 key | 삭제/이름 변경 금지 |
| UserFront 기존 QR 화면 | 삭제 금지, feature flag fallback 유지 |
| UserFront 기존 링크 화면 | QR 안정화 전까지 변경 금지 |
| OIDC Hydra/Kratos 연동 | 신규 metadata API는 Hydra/Kratos accept 흐름에 개입하지 않음 |
8.2 호환성 원칙
기존 클라이언트는 새 API를 몰라도 정상 동작해야 한다.
기존 UserFront -> 기존 API만 사용 -> 정상
신규 UserFront flag OFF -> 기존 화면 사용 -> 정상
신규 UserFront flag ON -> approval/info 조회 후 기존 approve API 사용 -> 정상
8.3 실패 시 fallback
approval/info 조회가 실패하면 아래 정책 중 하나를 선택한다.
권장 기본값:
운영/스테이징: 승인 차단 후 "요청 정보를 확인할 수 없습니다" 표시
개발 로컬: 기존 ApproveQrScreen fallback 허용
로컬 개발 중에는 빠른 검증을 위해 fallback을 허용하되, 운영에서는 요청 정보를 확인하지 못한 승인은 막는 쪽이 안전하다.
9. 개발 후 영향도 확인 체크리스트
9.1 Backend 테스트
필수 확인:
POST /api/v1/auth/approval/info정상 응답- 없는 pendingRef는 안전한 오류 반환
- QR init 후 approval info 조회 가능
- QR approve 기존 테스트 통과
- Enchanted Link init/poll 기존 테스트 통과
- Magic Link verify 기존 테스트 통과
- Login Code verify 기존 테스트 통과
- Password login 기존 테스트 통과
권장 테스트 파일:
backend/internal/handler/auth_handler_qr_test.go
backend/internal/handler/auth_handler_link_test.go
backend/internal/handler/auth_handler_login_code_test.go
backend/internal/handler/auth_handler_login_test.go
9.2 UserFront 테스트
필수 확인:
- feature flag OFF에서 기존 QR 승인 화면이 표시된다.
- feature flag OFF에서 QR 자동 승인이 기존처럼 동작한다.
- feature flag ON에서 공통 승인 화면이 표시된다.
내 요청입니다클릭 시 QR 승인 성공- 미로그인 승인 기기는 로그인 화면으로 이동
- approval info 조회 실패 시 안전 안내 표시
- 기존 로그인 링크 탭 동작 유지
- 기존 비밀번호 로그인 동작 유지
권장 테스트 파일:
userfront/test/qr_scan_screen_test.dart
userfront/test/login_navigation_race_test.dart
userfront/test/auth_proxy_service_test.dart
9.3 E2E 테스트
로컬에서 최소 2개 브라우저 또는 context로 확인한다.
시나리오:
- PC 브라우저에서 QR 로그인 시작
- 모바일 역할 브라우저에서 QR approve URL 접근
- 공통 승인 화면에서 요청 정보 확인
내 요청입니다클릭- PC 브라우저 poll이 성공하고 dashboard 진입
회귀 확인:
- 비밀번호 로그인
- 링크 로그인
- QR 로그인 flag OFF
- QR 로그인 flag ON
- OIDC RP callback
10. 보안 확인 항목
개발 완료 전 반드시 확인한다.
| 항목 | 기준 |
|---|---|
| IP 노출 | 전체 IP가 아니라 마스킹된 값만 사용자에게 표시 |
| User-Agent | 원문 저장은 가능하나 UI에는 요약 표시 권장 |
| pendingRef 오류 | 사용자 존재 여부를 추론할 수 없는 메시지 사용 |
| approval/info | 인증 없이 호출 가능한 경우 rate limit 필요 |
| QR 승인 | 승인 기기의 기존 세션 검증 유지 |
| 링크 승인 | verifyOnly 흐름 유지 |
| 감사 로그 | 승인/차단/실패 이벤트가 구분되어야 함 |
11. 롤백 방안
문제가 발생하면 아래 순서로 롤백한다.
- UserFront feature flag를 OFF로 변경한다.
- 기존
ApproveQrScreen경로로 즉시 복귀한다. - Backend 신규 API는 남겨도 기존 흐름에 영향이 없으므로 즉시 제거하지 않아도 된다.
- 신규 Redis key는 TTL 만료로 자연 정리된다.
- 문제가 Backend route 추가와 관련 있으면
/approval/info,/approval/blockroute만 비활성화한다.
롤백 시 제거하지 않아야 할 것:
- 기존 QR/link Redis key
- 기존
/qr/approve - 기존
/magic-link/verify - 기존
/login/code/verify - 기존
ApproveQrScreen
12. 권장 구현 범위
1차 로컬 구현 범위는 아래로 제한하는 것이 좋다.
- Backend
approval/infoAPI 추가 - QR init에 공통 metadata 병행 저장
- UserFront 공통 승인 화면 추가
- QR 승인 라우트만 feature flag로 공통 화면 연결
- 기존 QR 자동 승인 fallback 유지
- 테스트로 flag OFF/ON 영향도 확인
2차 구현 범위:
- Enchanted Link/Magic Link 화면 연결
approval/blockAPI 추가- poll 응답
blocked_by_user지원 - 감사 로그 차단 이벤트 추가
13. 최종 완료 기준
이 작업은 다음 조건을 모두 만족해야 완료로 본다.
- 기존 비밀번호 로그인에 영향이 없다.
- 기존 링크 로그인에 영향이 없다.
- 기존 QR 자동 승인 흐름은 feature flag OFF에서 그대로 동작한다.
- feature flag ON에서 QR 공통 승인 화면이 표시된다.
- 승인 화면에서 요청 기기/IP/시간/서비스가 표시된다.
내 요청입니다승인 후 원래 요청 브라우저가 로그인된다.- approval info 조회 실패 시 세션 발급으로 이어지지 않는다.
- Backend 단위 테스트와 UserFront 테스트가 통과한다.
- 신규 Redis key는 TTL을 가진다.
- 롤백 시 feature flag OFF만으로 기존 동작으로 돌아갈 수 있다.