Files
MyDoc/Baron SSO 로그인 화면 3가지 방식 절차 및 흐름.md

28 KiB

Baron SSO 로그인 화면 3가지 방식 절차 및 흐름 정리

작성일: 2026-06-22

1. 목적

Baron SSO 로그인 페이지에는 사용자가 선택할 수 있는 로그인 탭이 3개 있다.

  • 비밀번호
  • 로그인 링크
  • QR 코드

본 문서는 각 탭이 어떤 상황에서 쓰이고, 사용자가 어떤 절차를 거치며, 내부 시스템에서는 어떤 데이터와 인증 상태가 이동하는지 설명한다.

2. 로그인 화면 기준 요약

사용자 입력/행동 대표 Backend API 인증 성격 적합한 상황
비밀번호 이메일 또는 휴대폰 번호 + 비밀번호 입력 POST /api/v1/auth/password/login Kratos password 기반 직접 인증 가장 기본적인 로그인, 최초 로그인, 비밀번호를 알고 있는 경우
로그인 링크 이메일 또는 휴대폰 번호 입력 후 수신한 링크/코드 승인 POST /api/v1/auth/enchanted-link/init -> poll, POST /api/v1/auth/magic-link/verify, POST /api/v1/auth/login/code/verify Baron pendingRef 기반 원격 승인 비밀번호 입력을 줄이고 싶거나, 다른 기기/채널로 본인 확인하려는 경우
QR 코드 로그인 화면에 QR 표시, 이미 로그인된 기기에서 스캔/승인 POST /api/v1/auth/qr/init -> poll -> approve 교차 기기 승인 PC에서 로그인하려는데 모바일 또는 다른 브라우저에 이미 로그인되어 있는 경우

핵심 차이는 다음과 같다.

  • 비밀번호: 현재 로그인 화면에서 사용자가 직접 비밀번호를 증명한다.
  • 로그인 링크: 현재 로그인 화면은 대기하고, 이메일/SMS/링크를 받은 쪽에서 승인을 수행한다.
  • QR 코드: 현재 로그인 화면은 QR을 띄우고, 이미 로그인된 승인 기기가 해당 QR을 승인한다.

3. 공통 운영 경로

운영 환경에서는 사용자의 요청이 Backend, Kratos, Hydra로 직접 들어가지 않는다. 일반적으로 앞단의 L7 또는 Traefik, Baron Gateway Nginx, Oathkeeper를 거쳐 내부 서비스로 라우팅된다.

각 구성요소를 간단히 설명하면 다음과 같다.

구성요소 쉬운 역할 로그인 흐름에서 하는 일
L7 또는 Traefik 외부 출입구 / 1차 교통정리 사용자가 접속한 도메인과 HTTPS 요청을 받아 Baron SSO 쪽으로 넘긴다. 운영 환경에 따라 Traefik, 외부 L7 로드밸런서, Ingress, ALB 같은 장비가 이 역할을 한다.
Baron Gateway Nginx Baron SSO 내부 안내 데스크 들어온 URL 경로를 보고 UserFront, Backend, Oathkeeper 중 어디로 보낼지 나눈다. 예를 들어 /api/*는 Backend, /oidc/*는 Oathkeeper/Hydra 쪽으로 보낸다.
Oathkeeper Ory 앞단 보안 게이트 /oidc/*, /auth/*처럼 Ory로 가는 요청을 받아 정책에 맞게 통과시키고, 내부 Ory 주소로 전달한다. 현재 흐름에서는 Hydra/Kratos 앞단의 보호 프록시 역할을 한다.
Ory Hydra OIDC/OAuth2 인가 서버 RP의 client_id, redirect_uri, scope, state, code_challenge를 확인하고, 로그인 필요 시 login_challenge를 만들어 UserFront 로그인 화면으로 보낸다.
UserFront Baron SSO 로그인 화면 사용자가 보는 비밀번호, 로그인 링크, QR 코드 탭 화면이다.

따라서 L7, Nginx, Oathkeeper는 사용자를 인증하는 주체라기보다 "요청을 올바른 내부 서비스로 안전하게 전달하는 앞단 계층"에 가깝다. 실제 로그인 요청 접수와 OIDC 판단은 Hydra가 하고, 비밀번호 검증과 세션 발급은 Backend와 Kratos가 처리한다.

중요한 점은 모든 요청이 Oathkeeper를 거치는 것은 아니라는 것이다. Oathkeeper는 주로 Ory Public endpoint 앞단에 붙어 있다. UserFront 화면 요청과 Baron Backend API 요청은 Nginx에서 각각 UserFront와 Backend로 직접 라우팅된다.

요청 종류별 경로는 다음과 같이 구분된다.

요청 종류 예시 실제 경로 Oathkeeper 경유 여부
로그인 화면 요청 /, /ko/login?login_challenge=... 브라우저 -> L7 -> Nginx -> UserFront 경유하지 않음
Baron Backend API 요청 /api/v1/auth/password/login, /api/v1/auth/qr/init UserFront -> L7 -> Nginx -> Backend 경유하지 않음
Ory Public 요청 /oidc/oauth2/auth, /auth/* 브라우저/RP -> L7 -> Nginx -> Oathkeeper -> Hydra/Kratos Public 경유함
Backend 내부 백채널 GetLoginRequest, AcceptLoginRequest, Kratos 세션 발급 Backend -> Hydra Admin/Kratos API/Redis 경유하지 않음

즉 로그인 화면이 표시될 때의 UserFront 요청은 Oathkeeper를 거치지 않는다. 반면 RP가 처음 보낸 /oidc/oauth2/auth 요청은 Ory Public endpoint이므로 Nginx에서 Oathkeeper로 보내고, Oathkeeper가 Hydra로 전달한다.

헷갈리기 쉬운 두 경로를 분리하면 다음과 같다.

로그인 화면 요청:
사용자 브라우저 -> L7 -> Baron Gateway Nginx -> UserFront

OIDC 인증 요청:
사용자 브라우저/RP -> L7 -> Baron Gateway Nginx -> Oathkeeper -> Hydra

로그인 버튼/API 요청:
UserFront -> L7 -> Baron Gateway Nginx -> Baron Backend

Backend 내부 완료 처리:
Baron Backend -> Hydra Admin API

다만 위 표는 "요청 종류별 경로"를 나눈 것이고, 사용자가 RP에 접속한 뒤 Baron SSO 로그인 화면에 도달하기까지의 실제 시간 순서는 아래처럼 하나의 흐름으로 이어진다.

3.1 RP 접속부터 Baron SSO 로그인 화면 도달까지

사용자가 RP 업무시스템에 접속했는데 RP 세션이 없을 때, Baron SSO 로그인 화면이 뜨기까지의 순서는 다음과 같다.

1. 사용자 브라우저 -> RP 업무시스템
   사용자가 RP의 보호 페이지에 접속한다.

2. RP 업무시스템
   RP가 자기 시스템의 로그인 세션을 확인한다.
   세션이 없으면 Baron SSO로 보내야 한다고 판단한다.

3. RP 업무시스템 -> 사용자 브라우저
   RP가 브라우저에게 Baron SSO OIDC 인증 URL로 이동하라고 응답한다.
   예: /oidc/oauth2/auth?client_id=...&redirect_uri=...&scope=...&state=...

4. 사용자 브라우저 -> L7 -> Baron Gateway Nginx
   브라우저가 Baron SSO의 /oidc/oauth2/auth 주소로 이동한다.

5. Baron Gateway Nginx -> Oathkeeper -> Hydra
   Nginx는 /oidc/* 경로이므로 Oathkeeper로 보낸다.
   Oathkeeper는 이를 Hydra Public endpoint로 전달한다.

6. Hydra
   client_id, redirect_uri, scope, state, nonce, code_challenge 등을 확인한다.
   즉, RP의 OIDC 로그인 요청이 정상인지 접수/검증한다.

7. Hydra
   사용자의 Baron SSO 로그인 세션이 없으면 login_challenge를 만든다.

8. Hydra -> 사용자 브라우저
   Hydra가 브라우저에게 UserFront 로그인 화면으로 이동하라고 리다이렉트한다.
   예: /login?login_challenge=...

9. 사용자 브라우저 -> L7 -> Baron Gateway Nginx -> UserFront
   브라우저가 실제 로그인 화면 URL을 다시 요청한다.
   이 요청은 / 또는 /login 화면 요청이므로 Oathkeeper를 거치지 않고 UserFront로 간다.

10. UserFront -> 사용자 브라우저
    비밀번호 / 로그인 링크 / QR 코드 탭이 있는 Baron SSO 로그인 화면이 표시된다.

핵심은 "두 번의 브라우저 이동"이 있다는 점이다.

첫 번째 이동은 RP가 Baron SSO의 OIDC endpoint로 보내는 이동이다.

사용자 브라우저 -> L7 -> Nginx -> Oathkeeper -> Hydra

두 번째 이동은 Hydra가 UserFront 로그인 화면으로 보내는 이동이다.

사용자 브라우저 -> L7 -> Nginx -> UserFront

따라서 "로그인 화면 요청은 Oathkeeper를 거치지 않는다"는 말은 두 번째 이동에 대한 설명이다. 반대로 "RP의 OIDC 인증 요청은 Oathkeeper를 거친다"는 말은 첫 번째 이동에 대한 설명이다.

3.2 Hydra가 먼저 나오는 이유

처음 흐름을 보면 Hydra가 Kratos보다 먼저 등장하기 때문에 혼동될 수 있다. 직관적으로는 "사용자 인증은 Kratos가 담당하니 Kratos가 먼저 나와야 하는 것 아닌가?"라고 생각할 수 있다.

그러나 Baron SSO의 RP 로그인 흐름에서는 사용자가 처음부터 Baron SSO 로그인 페이지에 직접 온 것이 아니다. 사용자는 먼저 RP 업무시스템에 접속하려고 한다. RP는 자기 세션이 없으면 Baron SSO의 OIDC endpoint로 브라우저를 보낸다.

이 시점에 Baron SSO가 먼저 판단해야 하는 것은 사용자의 비밀번호가 맞는지가 아니다. 먼저 확인해야 하는 것은 "어느 RP 시스템이 어떤 OIDC 로그인 요청을 보냈는가"이다.

역할을 나누면 다음과 같다.

질문 담당
어느 RP가 로그인을 요청했는가? Hydra
이 RP의 client_id가 정상인가? Hydra
redirect_uri가 등록된 주소인가? Hydra
요청한 scope, state, nonce, code_challenge가 정상인가? Hydra
사용자가 실제 누구인가? Kratos
비밀번호, 코드, 세션이 유효한가? Kratos
인증된 사용자를 RP 로그인 요청에 연결하는가? Baron Backend -> Hydra

따라서 Hydra가 먼저 하는 일은 "사용자 인증"이 아니라 "RP 로그인 요청 접수"이다. 이때 생성되는 login_challenge도 사용자 식별자가 아니라 RP 로그인 요청 식별자다.

쉽게 표현하면 다음과 같다.

Hydra = RP 로그인 신청 접수처
Kratos = 사용자 본인확인 창구
Baron Backend = 두 시스템을 연결해서 완료 처리하는 중계자

전체 흐름은 다음처럼 이해하면 된다.

1. RP 로그인 요청 접수
   담당: Hydra

2. 사용자 본인 인증
   담당: Kratos

3. 인증된 사용자를 RP 로그인 요청에 연결
   담당: Baron Backend -> Hydra

4. RP로 최종 복귀
   담당: Hydra

login_challenge는 "이 사용자가 누구인가"를 나타내는 값이 아니다. "이 RP 로그인 요청 건이 무엇인가"를 나타내는 임시 접수번호다. 사용자가 로그인 화면에서 비밀번호, 로그인 링크, QR 코드 중 하나로 인증을 마치면 Backend가 이 접수번호를 들고 Hydra에 AcceptLoginRequest를 호출하여 "이 접수 건은 이 사용자로 로그인 완료"라고 알려준다.

flowchart LR
    User[사용자 브라우저]
    RP[RP/업무시스템]
    L7[Traefik 또는 외부 L7]
    GW[Baron Gateway Nginx]
    UF[UserFront 로그인 화면]
    BE[Baron Backend]
    OK[Oathkeeper]
    KR[Ory Kratos]
    HY[Ory Hydra]
    Redis[Redis]

    User -->|업무시스템 접속| RP
    RP -->|OIDC 인증 요청 /oidc/oauth2/auth| L7
    User -->|Baron SSO 화면/API 요청| L7
    L7 --> GW
    GW -->|/| UF
    GW -->|/api/v1/*| BE
    GW -->|/auth/*| OK
    GW -->|/oidc/*| OK
    OK --> KR
    OK --> HY
    BE --> Redis
    BE --> KR
    BE --> HY

운영 관점에서는 다음 경로를 구분해서 설명하면 이해하기 쉽다.

구분 경로 의미
화면 진입 사용자 -> L7 -> Nginx -> UserFront 로그인 페이지를 보여주는 경로
Baron API UserFront -> L7 -> Nginx -> Backend 로그인 버튼 클릭, 링크 발송, QR 생성, 폴링 요청
Ory Public 브라우저 또는 Nginx -> Oathkeeper -> Kratos/Hydra Ory public API 또는 OIDC public endpoint
내부 백채널 Backend -> Kratos/Hydra/Redis 세션 발급, OIDC accept, 대기 상태 저장

4. 비밀번호 로그인

4.0 로그인 화면이 뜨기 전 순서

RP 화면에서 사용자가 로그인을 시도하면, 바로 비밀번호 검증이나 토큰 검증부터 하는 것이 아니다. 먼저 OIDC 인증 요청을 Baron SSO의 Hydra endpoint로 보낸다.

이때 Hydra가 확인하는 것은 주로 다음과 같다.

  • client_id: 어떤 RP 시스템이 요청했는지
  • redirect_uri: 로그인 후 돌아갈 주소가 등록된 주소인지
  • scope: RP가 요청한 권한 범위
  • state, nonce, code_challenge: OIDC/PKCE 흐름을 안전하게 이어가기 위한 값
  • 기존 Baron SSO 로그인 세션 존재 여부

즉, 처음 /oidc/oauth2/auth로 들어온 요청은 "토큰 확인"이라기보다 "OIDC 로그인 요청 접수 및 검증"에 가깝다. 사용자가 아직 Baron SSO에 로그인되어 있지 않으면 Hydra는 login_challenge를 만들고, 사용자를 Baron SSO 로그인 화면(UserFront)으로 보낸다.

흐름을 로그인 화면 표시 전까지만 보면 다음과 같다.

sequenceDiagram
    autonumber
    actor User as 사용자
    participant RP as RP/업무시스템
    participant L7 as L7/Traefik
    participant GW as Baron Gateway Nginx
    participant OK as Oathkeeper
    participant HY as Ory Hydra
    participant UF as UserFront 로그인 화면

    User->>RP: 업무시스템 접속 또는 보호 페이지 접근
    RP->>RP: RP 자체 세션 확인
    RP-->>User: RP 세션 없음, /oidc/oauth2/auth로 브라우저 리다이렉트
    User->>L7: GET /oidc/oauth2/auth?client_id=...&redirect_uri=...
    L7->>GW: 요청 전달
    GW->>OK: /oidc/* 경로 전달
    OK->>HY: strip_path 후 Hydra /oauth2/auth 전달
    HY->>HY: client_id, redirect_uri, scope, state, PKCE 검증
    alt 기존 Baron SSO 로그인 세션 없음
        HY-->>User: login_challenge 포함 UserFront 로그인 화면으로 리다이렉트
        User->>L7: UserFront 로그인 화면 요청
        L7->>GW: 화면 요청 전달
        GW->>UF: / 경로 UserFront로 전달
        UF-->>User: 비밀번호/로그인 링크/QR 코드 화면 표시
    else 기존 Baron SSO 로그인 세션 있음
        HY-->>User: 로그인 화면 생략 또는 consent/redirect 단계로 진행
    end

정리하면 다음 순서다.

  1. 사용자가 RP에 접속한다.
  2. RP가 client_id 등을 담아 Baron SSO의 /oidc/oauth2/auth로 브라우저를 보낸다.
  3. 요청은 L7, Baron Gateway Nginx, Oathkeeper를 거쳐 Hydra로 간다.
  4. Hydra는 RP 요청이 정상인지 확인하고, 기존 SSO 세션이 있는지 본다.
  5. 로그인 세션이 없으면 login_challenge를 발급하고 UserFront 로그인 화면으로 보낸다.
  6. 그 다음에야 사용자가 비밀번호/로그인 링크/QR 코드 중 하나를 선택한다.

4.1 사용자가 보는 절차

  1. 사용자가 비밀번호 탭을 선택한다.
  2. 이메일 또는 휴대폰 번호를 입력한다.
  3. 비밀번호를 입력한다.
  4. 로그인 버튼을 누른다.
  5. 인증 성공 시 Baron SSO 세션이 만들어지고, OIDC 로그인 중이면 원래 RP 시스템으로 돌아간다.
  6. 실패 시 비밀번호 오류, 미등록 사용자, 비활성 사용자 등의 메시지가 표시된다.

4.2 주요 상황

상황 처리
사용자가 이메일 입력 입력값을 그대로 loginId로 사용
사용자가 휴대폰 번호 입력 010-1234-5678 같은 값을 정리하여 +821012345678 형태로 정규화
일반 UserFront 로그인 sessionJwt를 받아 UserFront 세션으로 사용
RP OIDC 로그인 중 login_challenge를 함께 보내고, 성공 후 Hydra AcceptLoginRequest를 거쳐 redirectTo로 이동

4.3 데이터 예시

요청 예시:

{
  "loginId": "user@example.com",
  "password": "********",
  "login_challenge": "optional-hydra-login-challenge"
}

응답 예시:

{
  "status": "ok",
  "provider": "kratos",
  "sessionJwt": "eyJ...",
  "token": "eyJ...",
  "redirectTo": "https://sso.example.com/oidc/oauth2/auth?..."
}

redirectTo는 OIDC 로그인 흐름일 때 중요하다. RP 시스템에서 Baron SSO로 보낸 로그인 요청을 Hydra가 이어받고, Backend가 로그인 성공을 Hydra에 알려주면, Hydra가 최종 이동할 URL을 돌려준다.

4.4 흐름도

아래 흐름도는 "로그인 화면 표시 전 OIDC 요청 접수"와 "비밀번호 입력 후 세션 발급"을 한 번에 이어서 보여준다.

주의할 점은 4번의 /oidc/* -> Hydra 단계가 비밀번호나 access token을 검사하는 단계가 아니라는 것이다. 이 단계는 RP의 OIDC 인증 요청을 Hydra가 접수하고, client_idredirect_uri 등을 확인한 뒤 login_challenge를 만들어 UserFront 로그인 화면으로 보내는 단계다.

sequenceDiagram
    autonumber
    actor User as 사용자
    participant RP as RP/업무시스템
    participant L7 as L7/Traefik
    participant GW as Baron Gateway Nginx
    participant OK as Oathkeeper
    participant UF as UserFront
    participant BE as Baron Backend
    participant KR as Ory Kratos
    participant HY as Ory Hydra

    User->>RP: 업무시스템 접속
    RP-->>User: /oidc/oauth2/auth?client_id=... 로 리다이렉트
    User->>L7: GET /oidc/oauth2/auth?client_id=...
    L7->>GW: OIDC 요청 전달
    GW->>OK: /oidc/* 경로 전달
    OK->>HY: Hydra /oauth2/auth 전달
    HY->>HY: RP 요청 검증 및 login_challenge 생성
    HY-->>User: UserFront 로그인 화면으로 리다이렉트
    User->>UF: 로그인 화면 표시
    User->>UF: 이메일/휴대폰 + 비밀번호 입력
    UF->>L7: POST /api/v1/auth/password/login
    L7->>GW: API 요청 전달
    GW->>BE: /api/v1/auth/password/login
    BE->>KR: Kratos password 인증
    KR-->>BE: session_token, cookie
    alt login_challenge 있음
        BE->>HY: AcceptLoginRequest(login_challenge, subject)
        HY-->>BE: redirectTo
        BE-->>UF: sessionJwt + redirectTo
        UF->>RP: OIDC callback 이동
    else 일반 로그인
        BE-->>UF: sessionJwt
    end

5. 로그인 링크

5.1 사용자가 보는 절차

  1. 사용자가 로그인 링크 탭을 선택한다.
  2. 이메일 또는 휴대폰 번호를 입력한다.
  3. 로그인 링크 전송을 누른다.
  4. 현재 브라우저는 로그인 대기 상태가 된다.
  5. 사용자는 이메일/SMS로 받은 링크를 클릭하거나, 전달받은 코드를 입력한다.
  6. 승인 또는 코드 검증이 성공하면 원래 대기 중이던 브라우저가 로그인 완료된다.

5.2 주요 상황

상황 처리
이메일 입력 이메일로 로그인 링크 또는 코드 전달
휴대폰 번호 입력 SMS로 로그인 링크 또는 코드 전달
요청 브라우저 pendingRef를 들고 poll을 반복 호출
승인 브라우저/기기 링크 클릭 또는 코드 검증으로 pendingRef 승인
verifyOnly 승인 승인 기기에는 세션을 만들지 않고, 원래 요청 브라우저만 로그인 완료

로그인 링크 방식의 핵심은 pendingRef이다. pendingRef는 "로그인을 기다리는 요청"을 식별하는 임시 키다. 현재 브라우저는 이 키로 계속 상태를 확인하고, 링크를 클릭한 쪽이 해당 키를 승인하면 현재 브라우저가 세션을 받는다.

5.3 데이터 예시

초기 요청:

{
  "loginId": "user@example.com",
  "uri": "https://sso.example.com",
  "codeOnly": false
}

초기 응답:

{
  "linkId": "Sent",
  "pendingRef": "AbC123",
  "mode": "code",
  "provider": "kratos",
  "expiresIn": 300,
  "interval": 2,
  "resendAfter": 30
}

Redis 대기 상태 예시:

{
  "key": "enchanted_session:AbC123",
  "value": {
    "status": "pending",
    "loginId": "user@example.com"
  },
  "ttl": "5m"
}

폴링 중 대기 응답:

{
  "error": "authorization_pending",
  "code": "authorization_pending",
  "interval": 2
}

승인 후 성공 응답:

{
  "status": "ok",
  "sessionJwt": "eyJ..."
}

5.4 흐름도

sequenceDiagram
    autonumber
    actor User as 사용자
    participant UF as 요청 브라우저(UserFront)
    participant L7 as L7/Traefik
    participant GW as Baron Gateway Nginx
    participant BE as Baron Backend
    participant Redis as Redis
    participant KR as Ory Kratos/Courier
    participant Channel as Email/SMS
    participant Approver as 링크 승인 기기

    User->>UF: 로그인 링크 탭에서 이메일/휴대폰 입력
    UF->>L7: POST /api/v1/auth/enchanted-link/init
    L7->>GW: API 요청 전달
    GW->>BE: init 요청
    BE->>BE: 사용자 존재 여부 확인, 전화번호 정규화
    BE->>KR: 링크/코드 로그인 시작
    BE->>Redis: pendingRef 상태 저장(status=pending)
    KR-->>Channel: 로그인 링크 또는 코드 발송
    BE-->>UF: pendingRef, expiresIn, interval

    loop 요청 브라우저 대기
        UF->>BE: POST /api/v1/auth/enchanted-link/poll(pendingRef)
        BE->>Redis: pendingRef 상태 조회
        BE-->>UF: authorization_pending 또는 slow_down
    end

    User->>Approver: 수신한 링크 클릭 또는 코드 입력
    Approver->>BE: POST /api/v1/auth/magic-link/verify 또는 /login/code/verify
    BE->>Redis: pendingRef 승인 처리(status=approved/success)
    UF->>BE: poll 재시도
    BE-->>UF: sessionJwt

5.5 보안상 설명 포인트

로그인 링크는 비밀번호를 입력하지 않아 편리하지만, 링크를 받은 사람이 곧 승인자가 된다. 따라서 운영 설명 시 다음 통제가 필요하다.

  • 링크 만료 시간은 짧게 유지한다.
  • 재전송 제한과 폴링 속도 제한을 둔다.
  • 승인 화면에는 요청 기기, IP, 브라우저, 요청 시각을 표시한다.
  • "내가 요청한 로그인이 아님" 버튼을 제공한다.
  • 요청자와 승인자를 감사 로그에 분리 기록한다.

6. QR 코드 로그인

6.1 사용자가 보는 절차

  1. 사용자가 QR 코드 탭을 선택한다.
  2. 로그인 화면에 QR 코드가 표시된다.
  3. 사용자는 이미 로그인되어 있는 기기에서 QR을 스캔한다.
  4. 승인 기기에서 로그인 요청 정보를 확인하고 승인한다.
  5. 원래 QR을 띄운 브라우저가 로그인 완료된다.

6.2 주요 상황

상황 처리
PC에서 새 로그인 필요 PC 화면에 QR 표시
사용자가 모바일/다른 브라우저에 이미 로그인되어 있음 해당 기기로 QR 스캔 후 승인
승인 기기에 세션 없음 승인 불가, 먼저 승인 기기에서 로그인 필요
QR 만료 expired_token 처리 후 새 QR 발급 필요
폴링 과다 slow_down으로 폴링 간격 증가

QR 방식은 "이미 로그인된 기기"를 신뢰 근거로 쓴다. 즉 QR을 스캔하는 기기가 Baron SSO 세션을 가지고 있어야 하며, 그 세션의 사용자가 QR을 띄운 브라우저에 대한 로그인을 승인하는 구조다.

6.3 데이터 예시

QR 초기화 응답:

{
  "qrCode": "https://sso.example.com/ql/QR_REF_64_CHARS",
  "pendingRef": "PENDING_REF",
  "expiresIn": 300,
  "interval": 2
}

Redis 저장 예시:

[
  {
    "key": "enchanted_session:PENDING_REF",
    "value": {
      "status": "pending"
    },
    "ttl": "5m"
  },
  {
    "key": "qr_ref:QR_REF_64_CHARS",
    "value": "PENDING_REF",
    "ttl": "5m"
  }
]

승인 요청 예시:

{
  "pendingRef": "PENDING_REF"
}

승인 요청은 쿠키 세션 또는 토큰 세션을 통해 승인 사용자를 확인한다. 승인 후에는 QR을 띄운 브라우저 쪽 pendingRef가 성공 상태로 바뀐다.

6.4 흐름도

sequenceDiagram
    autonumber
    actor User as 사용자
    participant PC as 요청 PC(UserFront)
    participant L7 as L7/Traefik
    participant GW as Baron Gateway Nginx
    participant BE as Baron Backend
    participant Redis as Redis
    participant Mobile as 이미 로그인된 승인 기기
    participant KR as Ory Kratos

    User->>PC: QR 코드 탭 선택
    PC->>L7: POST /api/v1/auth/qr/init
    L7->>GW: API 요청 전달
    GW->>BE: QR init
    BE->>Redis: pendingRef/status=pending 저장
    BE->>Redis: qrRef -> pendingRef 매핑 저장
    BE-->>PC: qrCode URL, pendingRef, expiresIn
    PC->>PC: QR 코드 표시

    loop 요청 PC 대기
        PC->>BE: POST /api/v1/auth/qr/poll(pendingRef)
        BE->>Redis: pendingRef 상태 조회
        BE-->>PC: authorization_pending 또는 slow_down
    end

    User->>Mobile: QR 스캔
    Mobile->>BE: POST /api/v1/auth/qr/approve(pendingRef 또는 qrRef)
    BE->>KR: 승인 기기 세션 확인(whoami/session)
    BE->>KR: QR 대상 사용자의 코드 로그인/세션 발급 처리
    BE->>Redis: pendingRef/status=success, jwt 저장
    BE-->>Mobile: QR Login Approved
    PC->>BE: poll 재시도
    BE-->>PC: sessionJwt

6.5 QR 방식의 핵심 통제

QR 로그인은 편리하지만, 공격자가 자기 PC에 QR을 띄우고 사용자에게 스캔을 유도할 수 있다. 따라서 승인 화면에는 반드시 다음 정보가 필요하다.

승인 화면 표시 정보 이유
요청 기기 종류 내 PC인지 확인
요청 브라우저 평소 사용하는 브라우저인지 확인
요청 IP/대략 위치 낯선 위치 요청인지 확인
요청 시각 방금 내가 요청한 것인지 확인
RP 시스템 이름 어떤 업무시스템 로그인을 승인하는지 확인

또한 승인 버튼과 별도로 내가 요청한 로그인이 아님 버튼을 제공해야 한다. 이 버튼은 해당 pendingRef를 차단하고, 필요 시 세션/refresh grant revoke로 이어져야 한다.

7. 세 방식 비교

비교 항목 비밀번호 로그인 링크 QR 코드
현재 화면에서 인증 완료 가능 가능 불가능, 외부 링크/코드 필요 불가능, 승인 기기 필요
비밀번호 필요 필요 불필요 불필요
기존 로그인 기기 필요 불필요 필수는 아님 사실상 필요
이메일/SMS 채널 필요 불필요 필요 불필요
교차 기기 승인 아님 가능 핵심 기능
주요 임시 키 없음 pendingRef, magic token, login code pendingRef, qrRef
주요 위험 비밀번호 탈취, 무차별 대입 링크 탈취, 오발송, 피싱 승인 QR 피싱, 잘못된 승인
운영 통제 비밀번호 정책, 실패 횟수 제한 TTL, 재전송 제한, 승인 정보 표시 TTL, 승인 정보 표시, 기존 세션 검증

8. 팀 설명용 쉬운 표현

비밀번호 로그인은 "본인이 지금 이 화면에서 비밀번호를 직접 증명하는 방식"이다.

로그인 링크는 "지금 화면은 대기표를 뽑고, 이메일이나 문자로 받은 링크를 눌러 그 대기표를 승인하는 방식"이다.

QR 코드는 "새 PC 화면에 임시 출입증을 띄우고, 이미 로그인된 내 기기로 그 출입증을 스캔해서 승인하는 방식"이다.

세 방식 모두 최종 목적은 같다. Baron Backend가 사용자를 확인하고, Kratos 세션을 얻은 뒤, RP 로그인이면 Hydra OIDC 흐름을 완료하여 사용자를 원래 업무시스템으로 돌려보낸다. 다만 "사용자 본인임을 확인하는 수단"이 각각 다르다.

9. 운영/보안 권고

  1. 비밀번호 방식은 기본 로그인 수단으로 유지하되, 실패 횟수 제한과 감사 로그를 반드시 유지한다.
  2. 로그인 링크 방식은 만료 시간, 재전송 제한, 링크 재사용 방지를 강하게 적용한다.
  3. QR 방식은 승인 화면에 요청 정보를 충분히 보여주고, 사용자가 명확히 승인하도록 한다.
  4. 링크/QR 방식 모두 요청자 기기승인자 기기를 감사 로그에 분리 저장한다.
  5. 내가 요청한 로그인이 아님을 누르면 해당 pending 요청을 즉시 차단하고, 이미 발급된 세션 또는 refresh grant가 있으면 단계적으로 revoke한다.
  6. 내부 사용자는 Naver Works 같은 조직 채널 알림과 연동할 수 있지만, 외부 사용자는 이메일/패스키/TOTP 등 별도 신뢰 채널을 마련해야 한다.