eID 회원가입 API (v2)
EU 사용자가 gematik eID(fbeta) 흐름으로 신규 가입하는 v2 endpoint. 본 endpoint 두 개(register/eid/initialize · register/eid/complete) 의 spec 만 본 페이지에 두고, 사전 단계(App Token, 약관, Email OTP, AppCheck, fbeta OAuth chain)는 공통 사전 단계 페이지의 anchor 로 참조한다.
- 인증 채널은
Authorization: Bearer한 줄. - deviceId 는 token payload 에서. body 에
deviceId를 보내면 400 VALIDATION_ERROR. - signup → 로그인 세션: complete 응답은 login/eid 와 동일한
LoginV2ResponseDto(tokens + user + profile + roles + permissions + agreements). 클라이언트는 access token 을 저장해 바로 (재)로그인하거나 그대로 사용한다 — 가입 직후 별도 first-login 을 반복하지 않는다. - AppCheck verify-only:
/v2/security/appcheck/verify가{verified, reason?}만 반환하며 보호 endpoint 에 별도 헤더가 붙지 않음. - 마스터 문서: Auth 도메인 endpoints (eID 회원가입).
0. 호출 순서
| # | API | Authorization | body 핵심 | 응답에서 추출할 값 |
|---|---|---|---|---|
| 1 | POST /v2/auth/app/challenge | 없음 | { "deviceId" (object), "deviceIdHash" } | challenge, nonce |
| 2 | POST /v2/auth/app/complete-challenge | 없음 | { "deviceIdHash", "encryptedChallenge" } | appToken (이후 ③~⑨ Authorization) |
| 3 | GET /v2/mobile/app-settings | 없음 (User-Agent 필수) | — | 약관 versionId 목록 |
| 4 | POST /v2/auth/email/verification-code | Bearer <appToken> | { "email", "type": "REGISTRATION" } | requestId |
| 5 | POST /v2/auth/email/verify | Bearer <appToken> | { "email", "code", "requestId", "type": "REGISTRATION" } | verificationId |
| 6 | POST /v2/security/appcheck/verify | Bearer <appToken> | { "token", "platform" } | { verified: true } |
| 7 | POST /v2/auth/register/eid/initialize ⭐ | Bearer <appToken> | { "redirectUri" } | authUrl, state |
| 8 | (외부) fbeta OAuth chain | — | — | code, state (callback) |
| 9 | POST /v2/auth/register/eid/complete ⭐ | Bearer <appToken> | { "code", "state", "email", "emailVerificationId", "agreements", "profile" } | LoginV2ResponseDto (tokens + user) |
⭐ 표시 endpoint 가 본 가입의 본 호출. 나머지는 공통 사전 단계 페이지에 spec 이 있다.
1. POST /v2/auth/register/eid/initialize ⭐
OAuth state(10분 TTL)와 fbeta authorize URL 을 발급한다. 클라이언트는 응답의 authUrl 을 ASWebAuthenticationSession/CustomTabsIntent 로 열어 사용자 eID 인증을 수행한다.
- Path:
POST /v2/auth/register/eid/initialize - 인증: App Token 필요 (app token 검증)
- Rate Limit: 5/분
- 🔗 라이브 명세 (Swagger UI): dev
Request
| Header | Value |
|---|---|
Authorization | Bearer <appToken> |
Content-Type | application/json |
{
"redirectUri": "sleepqde://app/oauth2/callback"
}
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
redirectUri | string | Yes | fbeta 인증 완료 후 돌아올 deep link. |
deviceId 는 app token payload 에서 가져온다. body 에 추가하면 forbidNonWhitelisted 로 400 VALIDATION_ERROR.
Response 200 OK
{
"authUrl": "https://eid-de-dev.weltcorp.com/auth/authorization?client_id=...&state=...&code_challenge=...&redirect_uri=...",
"state": "abc123xyz...",
"expiresIn": 600,
"cohort": "standard"
}
| 필드 | 설명 |
|---|---|
authUrl | 서버가 PKCE·state·scope을 모두 채운 완성된 OAuth authorize URL. |
state | complete 호출 시 동일성 검증 키 (base64url 32자, 10분 TTL). |
expiresIn | state TTL (초). |
cohort | 가입 시점은 항상 "standard" — clinical 분기는 서비스 활성화 시점에 결정. |
PKCE code_verifier·code_challenge·nonce 는 응답에 노출되지 않고 서버가 서버에 보관한다 — 클라이언트는 state 만 받아두면 complete 단계에서 서버가 복원해 fbeta 토큰 교환을 수행한다.
Errors
| HTTP | code | message | 발생 조건 |
|---|---|---|---|
| 400 | 1001 | VALIDATION_ERROR | redirectUri 누락/형식 오류 또는 unknown 필드(deviceId 등) 포함 |
| 401 | 1000 | App token not provided / Missing deviceId claim in app token payload | app token 검증 실패 또는 payload 에 deviceId 누락 |
| 429 | 1000 | ThrottlerException: Too Many Requests | Endpoint throttle 5/분 |
2. POST /v2/auth/register/eid/complete ⭐
fbeta callback 에서 회수한 code+state 와 약관·이메일 OTP 검증 ID 를 받아 User+Profile+EidLink 를 생성하고, login/eid 와 동일한 LoginV2ResponseDto 세션을 반환한다 — 가입 직후 별도 first-login 을 반복하지 않는다.
- Path:
POST /v2/auth/register/eid/complete - 인증: App Token 필요 (app token 검증)
- Rate Limit: 5/분 (prod 상시 적용; dev/stage/local은
THROTTLE_DISABLED=true설정 시에만 해제) - 🔗 라이브 명세 (Swagger UI): dev
Request
| Header | Value |
|---|---|
Authorization | Bearer <appToken> |
Content-Type | application/json |
{
"code": "auth_code_from_fbeta",
"state": "abc123xyz...",
"email": "user@example.com",
"emailVerificationId": "verif_uuid_from_email_verify",
"agreements": [
{
"versionId": "agv_xxx",
"isAgreed": true
}
],
"profile": {
"userName": "Erika Mustermann",
"language": "de-DE",
"timezone": {
"id": "Europe/Berlin",
"offsetInMinutes": 60
}
}
}
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
code | string | Yes | fbeta 가 redirect 로 전달한 OAuth authorization code (1회용). |
state | string | Yes | 1 에서 받은 state 그대로. |
email | string | Yes | 사용자 이메일 (RFC 5322 형식, 최대 254자). |
emailVerificationId | string | Yes | Email OTP verify (type=REGISTRATION) 가 반환한 verificationId. |
agreements | array | Yes | 약관 동의 항목. { versionId, isAgreed } 객체 배열. |
profile | object | No | language: en-US/de-DE/ko-KR, userName 최대 64자, timezone: { id, offsetInMinutes } (옵션). |
profile.timezone | object | No | id: IANA 타임존(예: Europe/Berlin, 최대 64자), offsetInMinutes: UTC 기준 분 오프셋. 생략 시 Europe/Berlin(+60) 기본값. |
deviceId, appCheckToken, platform 은 body 에 받지 않는다. body 에 포함하면 400 VALIDATION_ERROR.
Response 201 Created — 로그인 세션 (LoginV2ResponseDto)
login/eid 와 동일한 형태. REGISTERED 단계라 userCycle 은 생략되고 roles/permissions 는 빈 배열 — 서비스 활성화(POST /v2/auth/user-cycle/activate) 후 채워진다.
{
"tokens": [
{
"token": "<access-jwt>",
"type": "ACCESS_TOKEN",
"expiresIn": 3600,
"issuedAt": 1714523700000
},
{
"token": "<refresh-jwt>",
"type": "REFRESH_TOKEN",
"expiresIn": 1209600,
"issuedAt": 1714523700000
}
],
"user": {
"id": "c1e7c5cf-6fc2-4f4f-8e2f-9b8a3c5e2d1b",
"email": "user@example.com",
"questionnaireBundleId": "default-bundle-id",
"createdAt": 1714523700000
},
"profile": {
"language": "de-DE",
"timezone": {
"id": "Europe/Berlin",
"offsetInMinutes": 60
},
"userName": "Erika Mustermann"
},
"roles": [],
"permissions": [],
"agreements": []
}
cohort/region/sessionPolicy는 응답 body 에 포함되지 않는다(가입 시점엔 항상 standard/EU 로 고정이라 생략). cohort 분기는 서비스 활성화 시점에 결정된다.
DB 영향
- eID 연동 정보 저장 (status=ACTIVE).
- 사용자 레코드 생성 (service_state=REGISTERED, region=EU, cohort=standard).
userId 는 응답 LoginV2ResponseDto.user.id 에서 바로 확인된다.
Errors
| HTTP | code | message | 발생 조건 |
|---|---|---|---|
| 400 | 1001 | VALIDATION_ERROR | body 필드 누락/형식 오류 또는 unknown 필드 포함 |
| 400 | 2008 | EMAIL_NOT_VERIFIED | email+emailVerificationId 가 REGISTRATION OTP 캐시와 불일치/만료 |
| 400 | 2058 | SIGNUP_KVNR_INVALID | fbeta 가 반환한 KVNR 이 [A-Z][0-9]{9} 형식이 아니거나 검증 실패 |
| 400 | 2223 | EID_INVALID_CODE | fbeta authorization code 무효, 토큰 교환 실패, 또는 이미 다른 사용자에 연동된 eID |
| 400 | 2224 | EID_STATE_MISMATCH | OAuth state 가 서버 캐시와 불일치/만료/소진 (replay 차단) |
| 401 | 1000 | App token not provided / Missing deviceId claim in app token payload | app token 검증 실패 또는 payload 에 deviceId 누락 |
| 409 | 2024 | DUPLICATE_EMAIL | 이미 가입된 이메일 |
| 412 | 2080 | REQUIRED_AGREEMENTS_NOT_AGREED | 139e / A_26591 필수 약관 미동의 |
| 429 | 1000 | ThrottlerException: Too Many Requests | Endpoint throttle 5/분 (prod 상시 적용) |
다음 단계
- 첫 로그인 (REGISTERED → 첫 access/refresh): eID 로그인 1.
- 서비스 활성화:
POST /v2/auth/user-cycle/activate(Auth 도메인 endpoints (서비스 활성화)). - Native 2FA 가입: Native 2FA 회원가입.
- 공통 사전 단계 spec: 공통 사전 단계.
- 마스터 문서: Auth 도메인 endpoints (eID 회원가입).