35 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 TD
U[사용자 브라우저]
RP[RP/업무시스템]
EDGE[L7/Traefik + Baron Gateway Nginx]
OK[Oathkeeper]
UF[UserFront]
BE[Baron Backend]
KR[Ory Kratos]
HY[Ory Hydra]
R[Redis]
U -->|RP 보호 페이지 접근| RP
RP -->|/oidc/oauth2/auth| EDGE
EDGE -->|/oidc/*, /auth/*| OK
OK --> HY
OK --> KR
HY -->|login_challenge로 화면 이동| U
U -->|로그인 화면 요청 /| EDGE
EDGE --> UF
UF -->|/api/v1/*| EDGE
EDGE --> BE
BE --> KR
BE --> HY
BE --> R
운영 관점에서는 다음 경로를 구분해서 설명하면 이해하기 쉽다.
| 구분 | 경로 | 의미 |
|---|---|---|
| 화면 진입 | 사용자 -> 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, 대기 상태 저장 |
3.3 공통 토큰 저장 위치
비밀번호, 로그인 링크, QR 코드 중 어떤 방식으로 로그인하더라도 로그인 완료 후 등장하는 토큰 종류는 공통으로 이해해야 한다. Kratos가 발급하는 Baron SSO 세션 토큰과 Hydra가 발급하는 RP용 OAuth2/OIDC 토큰은 저장 주체와 용도가 다르다.
| 토큰/세션 | 발급 주체 | 받는 주체 | 일반 저장 위치 | 용도 |
|---|---|---|---|---|
Kratos session_token |
Kratos | Baron Backend -> UserFront | UserFront 브라우저 저장소 또는 Kratos 세션 쿠키 | Baron SSO/UserFront에서 사용자가 로그인되어 있음을 판단 |
| Kratos 세션 쿠키 | Kratos | Baron Backend가 응답 쿠키로 전달 | 사용자 브라우저 쿠키 | 쿠키 기반 SSO 세션 확인 |
| Hydra authorization code | Hydra | 브라우저를 통해 RP callback으로 전달 | 보통 임시값이며 장기 저장하지 않음 | RP가 token endpoint에서 토큰으로 교환 |
Hydra access_token |
Hydra | RP | RP 앱 유형에 따라 다름. 서버형 RP는 서버 세션/서버 저장소, SPA/PKCE는 브라우저 메모리 또는 제한적 브라우저 저장소 | RP가 API 호출 시 사용하는 접근 토큰 |
Hydra id_token |
Hydra | RP | RP 앱 유형에 따라 다름. 보통 RP 세션 생성 후 필요한 claim만 세션에 반영 | RP가 사용자 신원 claim을 확인 |
Hydra refresh_token |
Hydra | RP | 서버형 RP는 서버 저장소 또는 암호화된 세션 저장소 권장. 순수 브라우저 저장은 위험 | access token 재발급 |
| RP 로컬 세션 | RP | 사용자 브라우저 | RP 도메인의 세션 쿠키 + RP 서버 세션 저장소 | RP 내부 로그인 유지 |
현재 UserFront 웹 구현은 Kratos sessionJwt를 baron_auth_token 키로 브라우저 저장소에 보관할 수 있다. 쿠키 모드에서는 로컬 토큰을 제거하고 baron_auth_cookie_mode 플래그를 남겨 쿠키 기반 세션 확인으로 전환한다.
반대로 Hydra의 access_token, id_token, refresh_token은 Baron UserFront가 보관하는 토큰이 아니다. RP가 authorization code를 Hydra token endpoint에서 교환한 뒤 받는 토큰이므로, 저장 위치는 RP 구현 방식에 따라 결정된다.
운영 권고는 다음과 같다.
- server-side RP는 Hydra token을 브라우저 localStorage에 두지 말고 서버 세션 또는 서버 저장소에 보관한다.
- 브라우저에는 RP 자체 세션 쿠키만 내려주는 구조가 안전하다.
- PKCE SPA는 access token을 가능하면 메모리 중심으로 다루고, refresh token을 브라우저 장기 저장소에 두는 방식은 신중히 검토한다.
- 로그에는
sessionJwt,access_token,id_token,refresh_token, 쿠키 원문을 남기지 않는다.
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
정리하면 다음 순서다.
- 사용자가 RP에 접속한다.
- RP가
client_id등을 담아 Baron SSO의/oidc/oauth2/auth로 브라우저를 보낸다. - 요청은 L7, Baron Gateway Nginx, Oathkeeper를 거쳐 Hydra로 간다.
- Hydra는 RP 요청이 정상인지 확인하고, 기존 SSO 세션이 있는지 본다.
- 로그인 세션이 없으면
login_challenge를 발급하고 UserFront 로그인 화면으로 보낸다. - 그 다음에야 사용자가 비밀번호/로그인 링크/QR 코드 중 하나를 선택한다.
4.1 사용자가 보는 절차
- 사용자가
비밀번호탭을 선택한다. 이메일 또는 휴대폰 번호를 입력한다.비밀번호를 입력한다.- 로그인 버튼을 누른다.
- 인증 성공 시 Baron SSO 세션이 만들어지고, OIDC 로그인 중이면 원래 RP 시스템으로 돌아간다.
- 실패 시 비밀번호 오류, 미등록 사용자, 비활성 사용자 등의 메시지가 표시된다.
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 흐름도
아래 흐름도는 화면이 잘리지 않도록 세 단계로 나누어 표시한다.
주의할 점은 4번의 /oidc/* -> Hydra 단계가 비밀번호나 access token을 검사하는 단계가 아니라는 것이다. 이 단계는 RP의 OIDC 인증 요청을 Hydra가 접수하고, client_id와 redirect_uri 등을 확인한 뒤 login_challenge를 만들어 UserFront 로그인 화면으로 보내는 단계다.
4.4.1 RP 접속부터 로그인 화면 표시까지
sequenceDiagram
autonumber
actor User as 사용자
participant RP as RP/업무시스템
participant Edge as L7/Nginx
participant OK as Oathkeeper
participant HY as Ory Hydra
participant UF as UserFront
User->>RP: 업무시스템 접속
RP->>RP: RP 세션 확인
RP-->>User: /oidc/oauth2/auth로 리다이렉트
User->>Edge: GET /oidc/oauth2/auth?client_id=...
Edge->>OK: /oidc/* 전달
OK->>HY: Hydra /oauth2/auth 전달
HY->>HY: RP 요청 검증, login_challenge 생성
HY-->>User: UserFront 로그인 화면으로 리다이렉트
User->>Edge: UserFront 화면 요청
Edge->>UF: / 경로 전달
UF-->>User: 로그인 화면 표시
4.4.2 비밀번호 인증과 SSO 세션 생성
sequenceDiagram
autonumber 12
actor User as 사용자
participant UF as UserFront
participant Edge as L7/Nginx
participant BE as Baron Backend
participant KR as Ory Kratos
User->>UF: 이메일/휴대폰 + 비밀번호 입력
UF->>Edge: POST /api/v1/auth/password/login
Edge->>BE: /api/v1/auth/password/login
BE->>KR: Kratos password 인증
KR-->>BE: session_token, cookie
alt 일반 UserFront 로그인(login_challenge 없음)
BE-->>UF: sessionJwt
else RP OIDC 로그인(login_challenge 있음)
BE->>BE: Hydra 로그인 완료 처리로 계속 진행
end
4.4.3 RP OIDC 로그인 후속 처리
sequenceDiagram
autonumber 19
participant UF as UserFront
participant BE as Baron Backend
participant HY as Ory Hydra
participant RP as RP/업무시스템
BE->>HY: AcceptLoginRequest(login_challenge, subject)
HY-->>BE: redirectTo
BE-->>UF: sessionJwt + redirectTo
UF->>RP: OIDC callback 이동(code 포함)
RP->>HY: /oauth2/token에서 code 교환
HY->>HY: code 검증, client 검증, token 생성
HY-->>RP: access_token, id_token, refresh_token 반환
비밀번호 로그인에서도 토큰 저장 위치는 3.3 공통 토큰 저장 위치를 따른다. 즉 Kratos가 발급한 session_token은 UserFront 로그인 상태용으로 사용되고, RP에서 시작된 OIDC 흐름이면 Hydra가 후속으로 authorization code 및 RP용 access_token, id_token, refresh_token을 발급한다. Hydra 토큰은 UserFront가 아니라 RP가 자기 정책에 따라 저장한다.
5. 로그인 링크
5.1 사용자가 보는 절차
- 사용자가
로그인 링크탭을 선택한다. - 이메일 또는 휴대폰 번호를 입력한다.
로그인 링크 전송을 누른다.- 현재 브라우저는 로그인 대기 상태가 된다.
- 사용자는 이메일/SMS로 받은 링크를 클릭하거나, 전달받은 코드를 입력한다.
- 승인 또는 코드 검증이 성공하면 원래 대기 중이던 브라우저가 로그인 완료된다.
5.2 주요 상황
| 상황 | 처리 |
|---|---|
| 이메일 입력 | 이메일로 로그인 링크 또는 코드 전달 |
| 휴대폰 번호 입력 | SMS로 로그인 링크 또는 코드 전달 |
| 요청 브라우저 | pendingRef를 들고 poll을 반복 호출 |
| 승인 브라우저/기기 | 링크 클릭 또는 코드 검증으로 pendingRef 승인 |
| verifyOnly 승인 | 승인 기기에는 세션을 만들지 않고, 원래 요청 브라우저만 로그인 완료 |
로그인 링크 방식의 핵심은 pendingRef이다. pendingRef는 "로그인을 기다리는 요청"을 식별하는 임시 키다. 현재 브라우저는 이 키로 계속 상태를 확인하고, 링크를 클릭한 쪽이 해당 키를 승인하면 현재 브라우저가 세션을 받는다.
사용자 존재 여부 확인은 Backend 핸들러에서 시작하지만, Backend가 자체 DB만 보고 단독 판단하는 구조로 이해하면 안 된다. Backend는 IdpProvider.UserExists(loginId)를 호출하고, 현재 Ory provider 기준으로는 Kratos Admin API를 통해 해당 loginId의 identity가 있는지 확인한다.
즉 그림의 "사용자 존재 여부 확인"은 다음 의미다.
Baron Backend
-> IdpProvider.UserExists(loginId)
-> Ory/Kratos provider
-> Kratos Admin API 또는 IDP 조회
-> identity 존재 여부 반환
Backend가 이 확인을 먼저 하는 이유는 존재하지 않는 사용자에게 로그인 링크나 SMS를 보내지 않기 위해서다. 또한 휴대폰 번호 입력 시 010-1234-5678 같은 값을 +821012345678 형태로 정규화한 뒤 조회한다.
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..."
}
여기서 sessionJwt는 Backend가 임의로 새로 만든 토큰으로 이해하면 안 된다. 링크/코드 기반 흐름에서는 Backend가 Kratos에 코드 검증 또는 세션 발급을 요청하고, Kratos가 반환한 session_token을 Backend가 sessionJwt라는 응답 필드명으로 UserFront에 전달한다.
정리하면 다음과 같다.
토큰 발급 주체: Kratos
토큰 전달 주체: Baron Backend
UserFront 응답 필드명: sessionJwt
5.4 흐름도
아래 흐름도는 가로 폭이 커지지 않도록 로그인 링크 초기화, 승인 대기, OIDC 후속 처리로 나누어 표시한다.
5.4.1 로그인 링크 초기화
sequenceDiagram
autonumber
actor User as 사용자
participant UF as 요청 브라우저(UserFront)
participant Edge as L7/Nginx
participant BE as Baron Backend
participant KA as Ory Kratos/Admin
participant KR as Ory Kratos/Courier
participant Redis as Redis
participant Channel as Email/SMS
User->>UF: 로그인 링크 탭에서 이메일/휴대폰 입력
UF->>Edge: POST /api/v1/auth/enchanted-link/init
Edge->>BE: init 요청
BE->>BE: 입력값 정리 및 전화번호 정규화
BE->>KA: IdpProvider.UserExists(loginId)로 identity 조회
KA-->>BE: 사용자 존재 여부 반환
BE->>KR: 링크/코드 로그인 시작
BE->>Redis: pendingRef 상태 저장(status=pending)
KR-->>Channel: 로그인 링크 또는 코드 발송
BE-->>UF: pendingRef, expiresIn, interval
5.4.2 요청 브라우저 대기와 승인
sequenceDiagram
autonumber 11
actor User as 사용자
participant UF as 요청 브라우저
participant BE as Baron Backend
participant Redis as Redis
participant Approver as 링크 승인 기기
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 재시도
5.4.3 Kratos 세션 발급과 Hydra OIDC 완료
sequenceDiagram
autonumber 17
participant UF as 요청 브라우저
participant BE as Baron Backend
participant Redis as Redis
participant KR as Ory Kratos
participant HY as Ory Hydra
participant RP as RP/업무시스템
BE->>KR: Kratos 코드 검증/세션 발급 요청
KR-->>BE: Kratos session_token 반환
BE->>Redis: session_token을 pendingRef 성공 상태에 저장
alt RP OIDC 로그인 요청(login_challenge 있음)
BE->>HY: AcceptLoginRequest(login_challenge, subject)
HY-->>BE: redirectTo 반환
BE-->>UF: sessionJwt + redirectTo
UF->>RP: redirectTo 따라 RP callback 이동(code 포함)
RP->>HY: /oauth2/token에서 code 교환
HY->>HY: code 검증, client 검증, token 생성
HY-->>RP: access_token, id_token, refresh_token 반환
else UserFront 일반 로그인(login_challenge 없음)
BE-->>UF: sessionJwt 필드로 Kratos session_token 전달
end
위 그림의 마지막 단계는 두 가지로 나뉜다.
- RP에서 시작된 OIDC 로그인이라면
login_challenge가 있으므로 Backend가 Hydra에AcceptLoginRequest를 호출하고, RP는 최종적으로 Hydra에서access_token,id_token,refresh_token을 받는다. - Baron UserFront 자체 로그인이라면
login_challenge가 없으므로 Hydra 후속 단계 없이 Kratossession_token을sessionJwt로 받아 로그인 완료된다.
5.5 보안상 설명 포인트
로그인 링크는 비밀번호를 입력하지 않아 편리하지만, 링크를 받은 사람이 곧 승인자가 된다. 따라서 운영 설명 시 다음 통제가 필요하다.
- 링크 만료 시간은 짧게 유지한다.
- 재전송 제한과 폴링 속도 제한을 둔다.
- 승인 화면에는 요청 기기, IP, 브라우저, 요청 시각을 표시한다.
- "내가 요청한 로그인이 아님" 버튼을 제공한다.
- 요청자와 승인자를 감사 로그에 분리 기록한다.
6. QR 코드 로그인
6.1 사용자가 보는 절차
- 사용자가
QR 코드탭을 선택한다. - 로그인 화면에 QR 코드가 표시된다.
- 사용자는 이미 로그인되어 있는 기기에서 QR을 스캔한다.
- 승인 기기에서 로그인 요청 정보를 확인하고 승인한다.
- 원래 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 흐름도
QR 흐름도는 화면 폭을 줄이기 위해 L7과 Nginx를 L7/Nginx로 묶어 표현한다.
sequenceDiagram
autonumber
actor User as 사용자
participant PC as 요청 PC(UserFront)
participant Edge as L7/Nginx
participant BE as Baron Backend
participant Redis as Redis
participant Mobile as 이미 로그인된 승인 기기
participant KR as Ory Kratos
User->>PC: QR 코드 탭 선택
PC->>Edge: POST /api/v1/auth/qr/init
Edge->>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. 운영/보안 권고
- 비밀번호 방식은 기본 로그인 수단으로 유지하되, 실패 횟수 제한과 감사 로그를 반드시 유지한다.
- 로그인 링크 방식은 만료 시간, 재전송 제한, 링크 재사용 방지를 강하게 적용한다.
- QR 방식은 승인 화면에 요청 정보를 충분히 보여주고, 사용자가 명확히 승인하도록 한다.
- 링크/QR 방식 모두
요청자 기기와승인자 기기를 감사 로그에 분리 저장한다. 내가 요청한 로그인이 아님을 누르면 해당 pending 요청을 즉시 차단하고, 이미 발급된 세션 또는 refresh grant가 있으면 단계적으로 revoke한다.- 내부 사용자는 Naver Works 같은 조직 채널 알림과 연동할 수 있지만, 외부 사용자는 이메일/패스키/TOTP 등 별도 신뢰 채널을 마련해야 한다.