diff --git a/baron-sso_login_guide/BARONSSO_loginAPI_task.md b/baron-sso_login_guide/BARONSSO_loginAPI_task.md new file mode 100644 index 0000000..e7722d4 --- /dev/null +++ b/baron-sso_login_guide/BARONSSO_loginAPI_task.md @@ -0,0 +1,132 @@ +# BARON-SSO 통합 로그인 연동 가이드 (자산관리 시스템) + +이 문서는 Baron SSO(OpenID Connect IdP)를 자산관리 시스템에 통합 연동하기 위한 상세 가이드입니다. Headless 로그인 방식과 OIDC 표준을 기반으로 사용자 인증 및 정보 획득 과정을 설명하며, Baron SSO `devfront` 페이지에서의 애플리케이션 설정 방법과 연동 시 필요한 핵심 로직을 다룹니다. + +--- + +## 1. 개요 + +자산관리 시스템에 Baron SSO를 연동하여 통합 로그인을 구현합니다. 이를 통해 사용자는 Baron SSO에서 인증하고, 자산관리 시스템은 사용자의 신원을 확인하고 필요한 정보를 획득하게 됩니다. 본 가이드는 주로 **Headless 로그인** 방식을 사용하며, 이는 사용자에게 Baron SSO의 로그인 화면을 직접 노출하지 않고 자산관리 시스템 내에서 인증 과정을 처리하는 방식입니다. + +--- + +## 2. OIDC 핵심 개념 + +### 2.1. Headless Login +사용자가 IdP(Baron SSO)의 로그인 화면을 거치지 않고, RP(Relying Party, 자산관리 시스템) 내에서 직접 사용자 인증 정보를 입력받아 백채널(Back-channel)로 인증을 수행하는 방식입니다. + +### 2.2. Client Assertion +보안 강화를 위해 RP가 자신이 신뢰할 수 있는 앱임을 증명하는 JWT(JSON Web Token)입니다. 이는 중요한 인증 요청 시 함께 전송됩니다. + +### 2.3. 스코프 (Scopes) +사용자 정보에 접근하기 위한 권한 요청 단위입니다. +- `openid`: OIDC 인증 요청임을 명시하며 `id_token` 발급에 필수적입니다. +- `profile`: 사용자의 기본 프로필 정보(이름, 사번, 부서명 등) 접근 권한을 요청합니다. +- `email`: 사용자의 이메일 주소 정보 접근 권한을 요청합니다. + +### 2.4. 자동 승인 (Auto-Consent) +Headless 환경에서 사용자의 정보 제공 동의 화면 없이, RP 서버가 백그라운드에서 동의 과정을 자동으로 처리하는 로직입니다. + +--- + +## 3. Baron SSO (devfront) 애플리케이션 생성 및 설정 + +자산관리 시스템을 Baron SSO에 연동하기 위해 `devfront` 관리자 도구에서 Headless RP를 설정해야 합니다. 이때 IdP 서버가 RP의 JWKS 엔드포인트에 접속할 수 있어야 함을 유의하십시오. + +### 3.1. Step 1: 클라이언트 기본 정보 입력 + +1. `devfront`에 접속하여 **연동 앱 > 연동 앱 추가**를 클릭합니다. +2. **Name**: `asset-management-system` (또는 자산관리 시스템의 이름을 입력) +3. **Redirect URIs**: 자산관리 시스템의 콜백 경로를 입력합니다. + - 예: `http://[자산관리_시스템_IP_또는_도메인]:[포트]/callback` + - **주의**: 실제 자산관리 시스템에서 요청하는 Redirect URI와 여기서 등록한 값이 **완전히 일치**해야 인증 거부 에러가 발생하지 않습니다. +4. **Scopes**: `openid`, `profile`, `email`을 선택합니다. +5. **Type**: 반드시 `pkce`를 선택합니다. + +### 3.2. Step 2: Headless 기능 및 보안 설정 + +1. `pkce` 하단의 **"Headless Login (자체 로그인 UI 사용)"** 토글을 활성화합니다. +2. **JWKS URI**: **Baron SSO 서버에서 접근 가능한** 자산관리 시스템의 공개키 주소를 입력합니다. + - 예: `http://[자산관리_시스템_IP_또는_도메인]:[포트]/.well-known/jwks.json` + - **주의**: Baron SSO 서버에서 자산관리 시스템의 포트로 네트워크가 열려있어야 합니다. + +### 3.3. Step 3: 저장 및 확인 (JWKS 캐시 검증) + +1. **앱 생성** 버튼을 클릭하여 설정을 저장합니다. +2. 연동 앱 목록에서 생성된 앱이 **PKCE (Headless Login)** 유형인지 확인합니다. +3. 앱 상세 페이지 하단이나 설정 탭에서 **JWKS 캐시 상태**가 `Success`인지 반드시 확인합니다. 캐시 상태가 비어있거나 실패인 경우, 자산관리 시스템이 켜져 있는지 확인하고 **새로고침(Refresh)** 버튼을 눌러 수동으로 캐시를 갱신해야 합니다. + +--- + +## 4. 자산관리 시스템 연동 로직 + +### 4.1. 사번 로그인 (Headless Employee ID Login Flow) + +사용자가 자산관리 시스템에서 사번과 비밀번호를 입력하여 로그인하는 시퀀스입니다. + +1. **신호 요청**: 자산관리 시스템은 Baron SSO의 Authorization Endpoint(`oauth2/auth`)에 요청하여 `login_challenge`를 획득합니다. +2. **본인 인증**: 자산관리 시스템은 Private Key로 서명된 `client_assertion`(JWT)과 사용자의 사번, 비밀번호, `login_challenge`를 포함하여 Baron SSO의 Headless 로그인 API(`POST /api/v1/auth/headless/password/login`)를 호출합니다. +3. **권한 획득 (자동 승인 포함)**: 인증 성공 후 Baron SSO가 반환하는 `redirectTo` 주소를 추적하며, 이 과정에서 `consent` 화면이 감지되면 자산관리 시스템은 백그라운드에서 동의 API(`POST /api/v1/auth/consent/accept`)를 호출하여 자동으로 승인합니다. 최종적으로 `Authorization Code`를 획득합니다. +4. **인증키 발급**: 획득한 `Authorization Code`와 새로 생성한 `client_assertion`을 사용하여 Baron SSO의 Token Endpoint에 요청, `id_token`과 `access_token`을 발급받습니다. +5. **로그인 완료**: `id_token`을 검증하고 사용자 정보를 추출하여 자산관리 시스템의 세션을 생성하고 로그인 완료 처리합니다. + +### 4.2. 전화번호 인증 로그인 (Headless Phone Number Authentication Login Flow) + +사용자가 자산관리 시스템에서 전화번호를 입력하여 인증 링크를 받고, 모바일에서 승인하여 로그인하는 시퀀스입니다. + +1. **신호 요청**: 사번 로그인과 동일하게 `login_challenge`를 획득합니다. +2. **링크 발송 요청**: 자산관리 시스템은 `client_assertion`, `login_challenge`, 사용자의 전화번호(`loginId`)를 포함하여 Baron SSO의 인증 링크 발송 API(`POST /api/v1/auth/headless/link/init`)를 호출합니다. Baron SSO는 사용자에게 인증 링크를 발송하고 `pendingRef`를 반환합니다. +3. **인증 상태 폴링**: 자산관리 시스템은 `pendingRef`와 `client_assertion`을 포함하여 Baron SSO의 상태 확인 API(`POST /api/v1/auth/headless/link/poll`)를 주기적으로 호출합니다. 사용자가 모바일에서 링크를 클릭하여 인증을 완료하면, Baron SSO는 `redirectTo` 정보를 반환하며 폴링이 종료됩니다. +4. **이후 과정**: 폴링 결과로 받은 `redirectTo` 주소부터는 사번 로그인과 동일하게 표준 OIDC 흐름을 따릅니다 (자동 승인, `Authorization Code` 획득, `id_token` 발급, 세션 생성). + +### 4.3. 자동 승인 및 스코프 처리 (Auto-Consent and Scope Handling) + +자산관리 시스템은 `openid`, `profile`, `email`과 같은 스코프를 Baron SSO에 요청합니다. Headless 환경에서는 SSO 서버로부터 리다이렉트 주소에 `/consent`가 포함될 경우, 자산관리 시스템은 이를 감지하여 `consent_challenge`를 추출하고, 동의 상세 정보(`GET /api/v1/auth/consent`)를 요청하여 필요한 스코프를 확인합니다. 이후, 확인된 스코프를 모두 허용(`grant_scope`)한다고 Baron SSO의 동의 승인 API(`POST /api/v1/auth/consent/accept`)에 전송하여 자동으로 승인 과정을 처리합니다. + +### 4.4. Tenant 접근 제한 예외 처리 (Tenant Access Restriction Exception Handling) + +자산관리 시스템이 `tenant_access_restricted=true`로 설정되어 있고, 현재 사용자의 tenant가 허용된 `allowed_tenants`에 포함되지 않는 경우, Baron SSO는 `403` HTTP 상태 코드와 `code=tenant_not_allowed`를 응답합니다. 자산관리 시스템은 이 응답을 감지하여 사용자에게 적절한 에러 페이지(예: `/ko/error?error=tenant_not_allowed...`)로 리다이렉트하여 처리해야 합니다. + +--- + +## 5. 자산관리 시스템 로컬 설정 (.env) + +자산관리 시스템 프로젝트 루트의 `.env` 파일에 다음과 같이 Baron SSO 연동 설정을 구성합니다. + +```env +# 애플리케이션 실행 포트 (자산관리 시스템의 포트) +PORT=3000 # 예시 + +# OIDC IdP 설정 (Baron SSO 서버 주소) +CLIENT_ID=... # devfront에서 발급받은 클라이언트 ID +ISSUER=http://[BARON_SSO_IP_또는_도메인]/oidc + +# 리다이렉트 및 보안 설정 +# REDIRECT_URI는 DevFront에 등록한 Redirect URIs 중 하나와 정확히 일치해야 합니다. +REDIRECT_URI=http://[자산관리_시스템_IP_또는_도메인]:[포트]/callback +# JWKS_URI는 Baron SSO 서버가 자산관리 시스템으로 접속할 때 사용하는 주소와 일치해야 합니다. +JWKS_URI=http://[자산관리_시스템_IP_또는_도메인]:[포트]/.well-known/jwks.json + +# tenant 제한 시 이동시킬 userfront 에러 경로 +ERROR_LOCALE_PATH=/ko/error +``` + +--- + +## 6. 주의 사항 (네트워크 구성) + +- **서버 간 통신 (Server-to-Server)**: Headless 로그인은 Baron SSO 서버가 직접 자산관리 시스템의 `JWKS_URI`에 접속하여 서명을 검증합니다. 따라서 IdP 서버에서 자산관리 시스템의 포트로의 인바운드 통신이 허용되어야 합니다. 방화벽 및 네트워크 설정을 확인하십시오. +- **Redirect URI 일치**: 브라우저를 통해 접속할 주소가 IP라면, Redirect URI와 JWKS URI 모두 IP 기반 주소로 통일하는 것이 문제 발생 소지를 줄이는 가장 좋은 방법입니다. 도메인을 사용한다면 일관되게 도메인으로 설정해야 합니다. + +--- + +## 7. 실행 + +자산관리 시스템 프로젝트에서 다음 명령어를 실행하여 필요한 패키지를 설치하고 서버를 시작합니다. + +```bash +npm install +npm start +``` + +서버 실행 시 `keys.json` 파일이 자동으로 생성되며, `.env`에 설정된 `JWKS_URI` 경로로 공개키가 서빙되어 Baron SSO가 이를 검증에 사용할 수 있게 됩니다. diff --git a/baron-sso_login_guide/headless-consent-and-scope.md b/baron-sso_login_guide/headless-consent-and-scope.md new file mode 100644 index 0000000..faa07fd --- /dev/null +++ b/baron-sso_login_guide/headless-consent-and-scope.md @@ -0,0 +1,119 @@ +# Headless 스코프(Scope) 및 자동 승인(Auto-Consent) 가이드 + +이 문서는 Headless 로그인 환경에서 애플리케이션이 사용자 정보를 가져오기 위해 사용하는 **스코프(Scope)**의 개념과, IdP의 화면 없이 권한을 획득하는 **자동 승인(Auto-Consent)** 로직을 설명합니다. + +--- + +## 1. 요구 스코프 (Requested Scopes) + +데모 앱은 OIDC 표준에 따라 다음 스코프를 IdP(Baron SSO)에 요청합니다. + +| 스코프 | 필수 여부 | 역할 및 획득 데이터 | +| :-------- | :-------: | :------------------------------------------------------------------ | +| `openid` | **필수** | 해당 요청이 OIDC 인증임을 명시. `id_token` 발급을 위해 반드시 필요. | +| `profile` | 권장 | 사용자의 기본 프로필 정보(이름, 사번, 부서명 등) 접근 권한. | +| `email` | 권장 | 사용자의 이메일 주소 정보 접근 권한. | + +데모 앱은 최초 인증 요청(`GET /oauth2/auth`) 시 `scope=openid profile` 형식으로 권한을 요구합니다. + +--- + +## 2. 자동 승인(Auto-Consent) 흐름도 + +일반적인 OIDC 환경에서는 사용자에게 '정보 제공 동의' 화면이 노출되지만, Headless 환경에서는 **RP 서버가 이 과정을 백그라운드에서 자동으로 수행**합니다. + +```mermaid +sequenceDiagram + autonumber + participant Browser as 사용자 브라우저 + participant RP as 데모 앱 서버 (RP) + participant SSO as Baron SSO (IdP) + + RP->>SSO: 1. 본인 인증 요청 (전화번호) + SSO-->>RP: 2. 인증 성공 및 리다이렉트 (redirectTo: /consent...) + + Note over RP: [자동 승인 로직 시작] + RP->>RP: 3. 리다이렉트 경로 중 '/consent' 감지 + RP->>SSO: 4. 동의 상세 정보 요청 (GET /api/v1/auth/consent) + SSO-->>RP: 5. 요청된 스코프 정보 반환 (openid, profile 등) + + RP->>SSO: 6. 동의 승인 API 호출 (POST /api/v1/auth/consent/accept)
[grant_scope 포함] + SSO-->>RP: 7. 최종 리다이렉트 주소 반환 (code=...) + + Note over RP: [표준 OIDC 흐름 복귀] + RP->>SSO: 8. Token Exchange (Code -> id_token) + SSO-->>RP: 9. 사용자 정보가 담긴 id_token 발급 + RP-->>Browser: 10. 세션 생성 및 로그인 완료 안내 +``` +--- + +## 3. 핵심 구현 코드 (server.js) + +데모 앱의 `server.js` 내 `resolveRedirects` 함수는 SSO 서버로부터 오는 리다이렉트 주소를 추적하다가, 동의 화면(`consent_challenge`)이 나타나면 이를 가로채서 처리합니다. + +```javascript +if (currentUrl.includes("/consent")) { + console.log( + " [자동 승인] 사용자의 정보 제공 동의 화면이 감지되어 시스템이 자동으로 승인 중입니다.", + ); + + // 1. URL에서 consent_challenge 추출 + const consentUrl = new URL(currentUrl); + const consentChallenge = consentUrl.searchParams.get("consent_challenge"); + + // 2. SSO 서버에 현재 요청된 스코프가 무엇인지 확인 + const detailsUrl = new URL("/api/v1/auth/consent", currentUrl); + detailsUrl.searchParams.set("consent_challenge", consentChallenge); + const detailsRes = await fetch(detailsUrl.toString(), { + headers: { Cookie: nextCookies }, + }); + const consentInfo = await detailsRes.json(); + + // 3. 확인된 스코프(openid, profile 등)를 모두 허용(accept)한다고 서버에 전송 + const acceptUrl = new URL("/api/v1/auth/consent/accept", currentUrl); + const acceptRes = await fetch(acceptUrl.toString(), { + method: "POST", + headers: { "Content-Type": "application/json", Cookie: nextCookies }, + body: JSON.stringify({ + consent_challenge: consentChallenge, + grant_scope: consentInfo.requested_scope || ["openid", "profile"], + grant_access_token_audience: + consentInfo.requested_access_token_audience || [], + }), + }); + + const acceptPayload = await acceptRes.json(); + // 4. 승인 후 받은 최종 목적지(code 포함)로 이동 계속 + return resolveRedirects(acceptPayload.redirectTo, nextCookies, depth + 1); +} +``` + +## 4. Tenant 접근 제한 예외 흐름 + +Headless RP도 consent 단계에서 tenant 제한에 걸릴 수 있습니다. 이 경우는 일반적인 auto-consent 성공 흐름과 별도로 처리해야 합니다. + +조건: +- RP metadata에 `tenant_access_restricted=true` +- 현재 사용자의 tenant가 `allowed_tenants`에 없음 + +이때 Baron SSO 응답: +- `GET /api/v1/auth/consent` 또는 `POST /api/v1/auth/consent/accept` +- HTTP `403` +- body에 `code=tenant_not_allowed` + +데모 앱 처리 원칙: +- `redirectTo`가 없다고 가정하고 계속 진행하면 안 됩니다. +- `tenant_not_allowed`를 감지하면 `userfront` 에러 화면 URL로 변환해 브라우저를 이동시켜야 합니다. +- 기본 목적지는 `/ko/error?error=tenant_not_allowed&error_description=...&details=...` 입니다. + +운영 관점 해석: +- backend 로그에 `tenant_not_allowed`가 보이면 Baron SSO 제한은 정상입니다. +- 브라우저에서 `Invalid URL`이 보이면 RP가 `403` 응답을 잘못 소비하고 있다는 뜻입니다. + +--- + +## 4. 특징 및 장점 + +- **심리스한 UX**: 사용자는 "로그인 중..."이라는 메시지만 보게 되며, 복잡한 권한 동의 절차를 신경 쓸 필요가 없습니다. +- **강력한 보안**: 자동 승인 과정은 서버 대 서버(Back-channel) 통신으로 이루어지며, 브라우저에는 동의 관련 데이터가 전혀 노출되지 않습니다. +- **유연한 확장**: 나중에 새로운 사용자 정보(`email` 등)가 필요해지더라도, 앱의 스코프 설정만 변경하면 자동으로 승인 로직에 반영됩니다. diff --git a/baron-sso_login_guide/headless-employee-login-flow.md b/baron-sso_login_guide/headless-employee-login-flow.md new file mode 100644 index 0000000..2f2fd4f --- /dev/null +++ b/baron-sso_login_guide/headless-employee-login-flow.md @@ -0,0 +1,95 @@ +# Headless 사번 로그인 로직 + +이 문서는 사용자가 데모 애플리케이션에서 **사번과 비밀번호**를 이용해 로그인할 때 발생하는 내부 OIDC (OpenID Connect) Headless 인증 흐름을 설명합니다. + +## 핵심 개념 + +- **Headless Login**: 사용자가 IdP(Identity Provider, Baron SSO)의 로그인 화면을 거치지 않고, RP(Relying Party, 데모 앱) 내에서 직접 아이디와 비밀번호를 입력받아 백채널(Back-channel)로 인증을 수행하는 방식입니다. +- **Client Assertion**: 보안을 위해 데모 앱은 사번/비밀번호를 전송할 때 자신이 신뢰할 수 있는 앱임을 증명하는 JWT(`client_assertion`)를 생성하여 함께 전송합니다. + +--- + +## 1. 사번 로그인 시퀀스 다이어그램 + +```mermaid +sequenceDiagram + autonumber + actor User as 사용자 + participant RP as 데모 앱 (RP) + participant OIDC as Baron SSO (OIDC/Hydra) + participant Auth as Baron SSO (Headless API) + + User->>RP: 1. 사번 / 비밀번호 입력 후 로그인 요청 + + note over RP: [1단계: 신호 요청] + RP->>OIDC: 2. OIDC Discovery 및 Authorization Request
(GET /oauth2/auth?response_type=code...) + OIDC-->>RP: 3. login_challenge 발급 및 302 Redirect + + note over RP: [2단계: 본인 인증] + RP->>RP: 4. Private Key로 client_assertion (JWT) 생성 + RP->>Auth: 5. Headless 로그인 API 호출
(POST /api/v1/auth/headless/password/login)
[client_assertion, login_challenge, 사번, 비밀번호 포함] + Auth-->>RP: 6. 인증 성공 및 리다이렉트 URL 반환 + + note over RP: [3단계: 권한 획득] + RP->>OIDC: 7. 리다이렉트 URL 추적 (Cookie 유지) + OIDC-->>RP: 8. Consent 화면 (필요 시 자동 승인 API 호출) + OIDC-->>RP: 9. 최종 Authorization Code 발급 (code=...) + + note over RP: [4단계: 인증키 발급] + RP->>OIDC: 10. Token Endpoint 호출 (Authorization Code Grant)
[client_assertion 포함] + OIDC-->>RP: 11. id_token, access_token 발급 + + note over RP: [5단계: 로그인 완료] + RP->>RP: 12. id_token 검증 및 사용자 세션 생성 + RP-->>User: 13. 홈 화면 리다이렉트 및 로그인 성공 +``` + +--- + +## 2. 단계별 상세 로직 설명 + +위 다이어그램의 주요 단계를 데모 애플리케이션(`server.js`의 `runHeadlessSsoLogin` 함수) 로직을 중심으로 설명합니다. + +### [1단계] 신호 요청 (Authorization Request) +- **목적**: SSO 서버와의 세션을 시작하고, 인증 컨텍스트를 식별할 수 있는 고유값(`login_challenge`)을 획득합니다. +- **동작**: + 1. OIDC Discovery를 통해 엔드포인트들을 가져옵니다. + 2. `response_type=code`, `client_id`, `state`, `nonce` 등을 포함하여 Authorization Endpoint(`/oauth2/auth`)로 `fetch` 요청(리다이렉트 수동 추적 모드)을 보냅니다. + 3. 응답 헤더의 `Location`에 포함된 `login_challenge` 값을 추출합니다. + +### [2단계] 본인 인증 (Headless Password Login) +- **목적**: 사용자가 입력한 자격 증명(사번, 비밀번호)을 SSO 서버에 직접 전달하여 인증을 승인받습니다. +- **동작**: + 1. RP의 **Private Key(`keys.json`)**를 사용하여 `client_assertion` JWT를 서명합니다. (이때 `aud` 값은 SSO 서버의 Headless API URL이어야 합니다.) + 2. `POST /api/v1/auth/headless/password/login` 엔드포인트로 JSON 페이로드를 전송합니다. + ```json + { + "client_id": "...", + "login_challenge": "...", + "loginId": "사번", + "password": "비밀번호", + "client_assertion": "..." + } + ``` + 3. 인증이 성공하면 서버는 OIDC 로그인 흐름을 계속할 수 있는 `redirectTo` 주소를 반환합니다. + +### [3단계] 권한 획득 (Redirect Resolution & Consent) +- **목적**: 인증 성공 후, OIDC 프로토콜에 따라 최종적으로 `Authorization Code`를 획득합니다. +- **동작**: + 1. 2단계에서 받은 `redirectTo` 주소를 따라가며 쿠키(Cookie)를 계속 유지시킵니다. + 2. 만약 경로 중에 정보 제공 동의(`consent`) 화면이 감지되면, 데모 앱이 사용자 대신 백그라운드에서 동의 API(`POST /api/v1/auth/consent/accept`)를 호출하여 자동 승인합니다. + 3. 최종적으로 URL에 `code=...`가 포함된 리다이렉트를 찾습니다. + +### [4단계] 인증키 발급 (Token Exchange) +- **목적**: 획득한 `Authorization Code`를 안전한 토큰들로 교환합니다. +- **동작**: + 1. Token Endpoint로 코드를 전송합니다. + 2. 이때도 보안을 위해 `private_key_jwt` 방식이 사용되므로, 새로 생성한 `client_assertion`을 요청 본문에 포함시킵니다. + 3. 검증이 완료되면 `access_token`과 사용자 정보가 담긴 `id_token`을 받게 됩니다. + +### [5단계] 로그인 완료 (Session Creation) +- **목적**: 받은 토큰을 검증하고, 브라우저가 인식할 수 있는 로컬 세션을 만듭니다. +- **동작**: + 1. `jose` 라이브러리를 통해 `id_token`의 서명을 검증하고 payload(사번, 이름, 부서 등)를 추출합니다. + 2. 추출된 사용자 정보를 `express-session`의 `req.session.user`에 저장합니다. + 3. 클라이언트 브라우저에게 세션 쿠키를 발급하며 홈 화면(`/home.html`)으로 리다이렉트 시킵니다. diff --git a/baron-sso_login_guide/headless-phone-login-flow.md b/baron-sso_login_guide/headless-phone-login-flow.md new file mode 100644 index 0000000..27cb0e6 --- /dev/null +++ b/baron-sso_login_guide/headless-phone-login-flow.md @@ -0,0 +1,84 @@ +# Headless 전화번호 인증 로그인 로직 + +이 문서는 사용자가 데모 애플리케이션에서 **전화번호**를 입력하여 인증 링크를 받고, 모바일에서 승인하여 로그인하는 내부 흐름을 설명합니다. + +## 핵심 개념 + +- **Link Init**: 사용자의 전화번호로 실제 인증 링크(카카오톡 또는 SMS)를 발송하도록 SSO 서버에 요청하는 단계입니다. +- **Polling**: 사용자가 모바일에서 링크를 클릭할 때까지 앱 서버가 주기적으로 SSO 서버에 "인증이 완료되었는지" 물어보는 과정입니다. +- **Security**: 이 과정에서도 모든 API 호출은 RP의 Private Key로 서명된 `client_assertion`을 포함하여 보안을 유지합니다. + +--- + +## 1. 전화번호 로그인 시퀀스 다이어그램 + +```mermaid +sequenceDiagram + autonumber + actor User as 사용자 + participant RP as 데모 앱 (RP) + participant OIDC as Baron SSO (OIDC) + participant Auth as Baron SSO (Headless API) + + User->>RP: 1. 전화번호 입력 후 인증 요청 + + note over RP: [1단계: 신호 요청] + RP->>OIDC: 2. Authorization Request (GET /oauth2/auth)
[login_challenge 획득] + + note over RP: [2단계: 링크 발송 요청] + RP->>Auth: 3. 인증 링크 발송 요청 (POST .../link/init)
[client_assertion, login_challenge, 전화번호 포함] + Auth-->>User: 4. 인증 링크 발송 (카카오톡/SMS) + Auth-->>RP: 5. 대기 참조값(pendingRef) 반환 + + note over RP: [3단계: 인증 상태 폴링] + loop 인증 완료 시까지 반복 (최대 3분) + RP->>Auth: 6. 상태 확인 (POST .../link/poll)
[client_assertion, pendingRef 포함] + alt 아직 대기 중 + Auth-->>RP: 7. authorization_pending 응답 + RP->>RP: 잠시 대기 (interval) + else 사용자 링크 클릭 완료 + Auth-->>RP: 8. 인증 성공 및 리다이렉트 URL 반환 + end + end + + note over User: 사용자가 모바일에서 링크 클릭 및 승인 + + note over RP: [4단계: 이후 과정 (사번 로그인과 동일)] + RP->>OIDC: 9. 리다이렉트 추적 및 Authorization Code 획득 + RP->>OIDC: 10. Token Endpoint 호출 및 id_token 발급 + RP->>RP: 11. 세션 생성 및 홈 화면 이동 + RP-->>User: 12. 로그인 완료 안내 +``` + +--- + +## 2. 단계별 상세 로직 설명 + +### [1단계] 신호 요청 (Login Challenge) +- 사번 로그인과 동일하게 OIDC 표준 흐름을 시작하기 위해 `login_challenge`를 먼저 획득합니다. 이 값은 이후 인증 시도 시 "이 사용자가 어떤 OIDC 요청에 응답하고 있는지"를 SSO 서버에 알려주는 매개체가 됩니다. + +### [2단계] 링크 발송 요청 (Link Init) +- **동작**: `POST /api/v1/auth/headless/link/init` 호출. +- **데이터**: `client_assertion`, `login_challenge`, 사용자의 `loginId`(전화번호). +- **결과**: SSO 서버는 사용자의 전화번호로 인증 가능한 고유 링크를 발송하고, RP에게는 해당 요청을 추적할 수 있는 `pendingRef`와 다음 폴링까지의 권장 대기 시간(`interval`)을 반환합니다. + +### [3단계] 인증 상태 폴링 (Polling) +- **동작**: `POST /api/v1/auth/headless/link/poll`을 주기적으로 호출. +- **상태 처리**: + - `authorization_pending`: 사용자가 아직 링크를 클릭하지 않았으므로 지정된 시간만큼 기다린 후 다시 시도합니다. + - `slow_down`: 폴링 간격이 너무 잦다는 신호로, 대기 시간을 조금 더 늘립니다. + - `expired_token`: 3분이 경과하여 인증 요청이 만료된 경우입니다. + - **성공**: 사용자가 승인을 완료하면 서버는 `redirectTo` 정보를 반환하며 폴링이 종료됩니다. + +### [4단계] 이후 과정 (Standard OIDC Flow) +- 폴링 결과로 받은 `redirectTo` 주소부터는 표준 OIDC 흐름을 따릅니다. +- 데모 앱은 해당 주소로 리다이렉트하며 발생하는 쿠키를 유지하고, 필요 시 사용자 동의(Consent) 과정을 자동 수행하여 최종적으로 `Authorization Code`를 얻어냅니다. +- 마지막으로 해당 코드를 `id_token`으로 교환하여 사용자 정보를 세션에 저장합니다. + +--- + +## 3. 특징 및 주의 사항 + +- **비동기 사용자 경험**: 사용자는 PC에서 전화번호만 입력하고, 실제 인증 행위는 모바일에서 수행합니다. PC 화면은 폴링이 완료될 때까지 "인증 대기 중" 상태를 유지합니다. +- **보안 검증**: 폴링 시에도 `client_assertion`이 필요합니다. 이는 제3자가 `pendingRef`를 가로채더라도 RP의 비밀키 없이는 인증 상태를 훔쳐볼 수 없음을 보장합니다. +- **제한 시간**: 보안상 폴링은 보통 3분(180초)으로 제한되며, 이후에는 요청을 처음부터 다시 시작해야 합니다. diff --git a/baron-sso_login_guide/setup-guide.md b/baron-sso_login_guide/setup-guide.md new file mode 100644 index 0000000..521f3e3 --- /dev/null +++ b/baron-sso_login_guide/setup-guide.md @@ -0,0 +1,115 @@ +# Headless 로그인 데모 설정 가이드 (Baron SSO 연동) + +이 문서는 `headless-login-demo` 프로젝트를 설치하고, **Baron SSO (OIDC IdP)** 관리자 도구(`devfront`)에서 **Headless RP**를 올바르게 생성 및 설정하는 방법을 단계별로 설명합니다. + +--- + +## 1. Baron SSO (devfront) 설정 + +Headless RP는 일반 OIDC 클라이언트와 설정 방식이 다릅니다. 특히 **IdP 서버가 RP(데모 앱)의 JWKS 엔드포인트에 접속할 수 있어야 함**을 유의하십시오. + +### Step 1: 클라이언트 기본 정보 입력 + +1. `devfront` 접속 후 **연동 앱 > 연동 앱 추가**를 클릭합니다. +2. **Name**: `headless-login` (또는 자유롭게 입력) +3. **Redirect URIs**: 데모 앱에 접속할 주소의 콜백 경로를 입력합니다. + - **통합 환경 (IdP와 RP가 같은 서버일 때)**: `http://localhost:3000/callback` + - **분리 환경 (IdP는 원격 서버, RP는 내 PC일 때)**: `http://[내_PC_IP]:3000/callback` + - _주의: 실제 앱에서 요청하는 Redirect URI와 여기서 등록한 값이 **완전히 일치**해야 인증 거부 에러가 발생하지 않습니다._ +4. **Scopes**: `openid`, `profile`, `email`을 선택합니다. +5. **Type**: 반드시 `pkce`를 선택해야 합니다. + +![Step 1 설정 예시](./images/setup-step1-example.png) + +### Step 2: Headless 기능 및 보안 설정 + +1. `pkce` 하단의 **"Headless Login (자체 로그인 UI 사용)"** 토글을 활성화합니다. +2. **JWKS URI**: **Baron SSO 서버에서 접근 가능한** 데모 앱의 공개키 주소를 입력합니다. + - **통합 환경 (IdP와 RP가 같은 서버일 때)**: `http://localhost:3000/.well-known/jwks.json` + - **분리 환경 (IdP는 원격 서버, RP는 내 PC일 때)**: `http://[내_PC_IP]:3000/.well-known/jwks.json` + - _주의: Baron SSO 서버(`172.16.10.175`)에서 사용자님의 로컬 PC 포트로 네트워크가 열려있어야 합니다._ + +![Step 2 설정 예시](./images/setup-step2-example.png) + +### Step 3: 저장 및 확인 (JWKS 캐시 검증) + +1. **앱 생성** 버튼을 클릭해 저장합니다. +2. 연동 앱 목록에서 해당 앱이 **PKCE (Headless Login)** 유형으로 생성됐는지 확인합니다. +3. 앱 상세 페이지 하단이나 설정 탭에서 **JWKS 캐시 상태**가 `Success`인지 반드시 확인합니다. + - _팁: 캐시 상태가 비어있거나 실패인 경우, 데모 앱(내 PC)이 켜져 있는지 확인하고 **새로고침(Refresh)** 버튼을 눌러 수동으로 캐시를 갱신하세요. 캐싱이 정상적으로 이루어져야만 로그인이 작동합니다._ + +## ![Step 3 설정 예시](./images/setup-step3-example.png) + +## 2. Headless Login 애플리케이션 로컬 설정 (.env) + +`devfront`에서 설정한 값을 바탕으로 프로젝트 루트의 `.env` 파일을 구성합니다. + +```env +# 애플리케이션 실행 포트 +PORT=3000 + +# OIDC IdP 설정 (원격 서버 주소 입력) +CLIENT_ID=a9b64539-7242-4aa5-ad3d-13c7f1ef00f2 +ISSUER=http://172.16.10.175/oidc + +# 리다이렉트 및 보안 설정 +# REDIRECT_URI는 DevFront에 등록한 Redirect URIs 중 하나와 정확히 일치해야 합니다. +REDIRECT_URI=http://[내_PC_IP]:3000/callback +# JWKS_URI는 Baron SSO 서버가 내 PC로 접속할 때 사용하는 주소와 일치해야 합니다. +JWKS_URI=http://[내_PC_IP]:3000/.well-known/jwks.json + +# tenant 제한 시 이동시킬 userfront 에러 경로 +ERROR_LOCALE_PATH=/ko/error +``` + +--- + +## 3. 주의 사항 (네트워크 구성) + +- **서버 간 통신 (Server-to-Server)**: Headless 로그인은 Baron SSO 서버가 직접 데모 앱의 `JWKS_URI`에 접속하여 서명을 검증합니다. 따라서 IdP 서버에서 내 로컬 PC의 포트(`3000`)로의 인바운드 통신이 허용되어야 합니다. +- **Redirect URI 일치**: 브라우저를 통해 접속할 주소가 IP라면, Redirect URI와 JWKS URI 모두 IP 기반 주소로 통일하는 것이 문제 발생 소지를 줄이는 가장 좋은 방법입니다. + +--- + +## 4. 실행 + +```bash +npm install +npm start +``` + +> **💡 참고: 자동 키 생성 및 JWKS 엔드포인트 활성화** +> `npm start` 명령어로 서버를 실행하면, 프로젝트 내에 `keys.json` 파일이 자동으로 생성됩니다. 이 파일에는 서버가 자체적으로 발급한 RSA 보안 키 쌍(공개키/개인키)이 저장됩니다. +> 동시에, `.env`에 설정된 `JWKS_URI` 경로(또는 기본 경로)로 공개키가 서빙되어 Baron SSO가 검증에 사용할 수 있게 됩니다. + +--- + +## 5. Tenant 제한 테스트 가이드 + +Headless RP는 login API 성공 이후 consent 단계에서 tenant 제한이 걸릴 수 있습니다. 따라서 단순히 `/api/v1/auth/headless/password/login` 성공만 보면 안 되고, 최종 redirect 해석까지 확인해야 합니다. + +### 권장 검증 순서 + +1. 제한이 없는 상태에서 로그인 성공 여부를 먼저 확인합니다. +2. `devfront`에서 대상 RP에 `tenant_access_restricted`와 `allowed_tenants`를 설정합니다. +3. 허용된 tenant 사용자로 로그인해 정상 성공 여부를 확인합니다. +4. 허용되지 않은 tenant 사용자로 로그인해 `userfront`의 `/ko/error?error=tenant_not_allowed...` 화면으로 이동하는지 확인합니다. + +### 기대 동작 + +- `tenant_not_allowed`는 Baron SSO backend가 `403`으로 반환합니다. +- headless demo는 이 응답을 감지해 `userfront` 에러 화면으로 브라우저를 이동시킵니다. +- 따라서 브라우저 팝업으로 `Invalid URL`이 보인다면 demo 쪽 에러 처리 누락을 먼저 의심해야 합니다. + +### 로그 확인 포인트 + +```bash +docker logs --tail 100 headless-login-demo +docker logs --tail 100 baron_backend +``` + +- `headless-login-demo` + - consent 단계에서 `tenant_not_allowed`를 받아 `errorUrl`로 변환했는지 확인 +- `baron_backend` + - `GET /api/v1/auth/consent` 또는 `POST /api/v1/auth/consent/accept`가 `403`인지 확인 + - 해당 응답 body에 `code=tenant_not_allowed`가 포함되는지 확인 diff --git a/baron-sso_login_guide/setup-step1-example.png b/baron-sso_login_guide/setup-step1-example.png new file mode 100644 index 0000000..a51da34 Binary files /dev/null and b/baron-sso_login_guide/setup-step1-example.png differ diff --git a/baron-sso_login_guide/setup-step2-example.png b/baron-sso_login_guide/setup-step2-example.png new file mode 100644 index 0000000..6f0e92c Binary files /dev/null and b/baron-sso_login_guide/setup-step2-example.png differ diff --git a/baron-sso_login_guide/setup-step3-example.png b/baron-sso_login_guide/setup-step3-example.png new file mode 100644 index 0000000..d75179c Binary files /dev/null and b/baron-sso_login_guide/setup-step3-example.png differ diff --git a/docker-compose.prod.yaml b/docker-compose.prod.yaml index 539acdb..38910fd 100644 --- a/docker-compose.prod.yaml +++ b/docker-compose.prod.yaml @@ -40,7 +40,7 @@ services: ports: - "9090:80" volumes: - - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro + - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf - ./logs/nginx:/var/log/nginx depends_on: backend: