eID 로그인 API (v2)
EU 사용자가 gematik eID(fbeta) 흐름으로 로그인하는 v2 endpoint. Authorization: Bearer 가 담는 토큰 종류로 세 경로가 갈린다 — 각 경로는 사전 단계 호출이 다르므로 1·2·3 호출 순서를 따라가야 합니다. 본 endpoint 두 개(login/initialize·login/complete) 의 spec 은 LI·LC 마스터 섹션에 있습니다.
| 경로 | 시점 | Authorization | body OTP |
|---|---|---|---|
| 1 첫 로그인 (가입 직후 동일 기기) | 가입 직후 (REGISTERED) | app token | 필수 (type=LOGIN) |
| 2 동일 기기 재로그인 | 서비스 활성화 후 같은 디바이스에서 재진입 | user access token | 불필요 |
| 3 새 기기 첫 로그인 | 활성 사용자가 새 디바이스에서 처음 진입 | 새 기기 app token | 필수 (type=LOGIN) |
서버는 서버 라우팅 가 Authorization Bearer payload 종류로 분기 — user access token = 재로그인, app token + body OTP = first-login / new-device.
- 인증 채널은
Authorization: Bearer한 줄. - deviceId 는 token payload 에서. body 에
deviceId를 보내면 400 VALIDATION_ERROR. - AppCheck 는 verify-only. 단 verify 호출 자체는 app token 으로.
- 마스터 문서: Auth 도메인 endpoints (로그인).
1. 첫 로그인 (가입 직후 동일 기기)
eID 회원가입 2 가 ack-only 로 끝난 직후, 가입과 동일한 디바이스에서 첫 access/refresh 를 발급받는 경로. 가입 단계에서 쓰던 app token 을 그대로 재사용한다.
| # | API | Authorization | body 핵심 | 응답에서 추출할 값 |
|---|---|---|---|---|
| 1 | POST /v2/auth/email/verification-code | Bearer <appToken> (가입 때 그대로) | { "email", "type": "LOGIN" } | requestId |
| 2 | POST /v2/auth/email/verify | Bearer <appToken> | { "email", "code", "requestId", "type": "LOGIN" } | verificationId, lastLoginMethod |
| 3 | POST /v2/security/appcheck/verify | Bearer <appToken> | { "token", "platform" } | { verified: true } |
| 4 | POST /v2/auth/login/eid/initialize ⭐ | Bearer <appToken> | { "redirectUri" } | authUrl, state |
| 5 | (외부) fbeta OAuth chain | — | — | code, state (callback) |
| 6 | POST /v2/auth/login/eid/complete ⭐ | Bearer <appToken> | { "code", "state", "email", "emailVerificationId" } | LoginResponseDto |
① ② 의
type=LOGIN은 가입 때 받은 REGISTRATION OTP 와 별개 캐시이므로 새로 발급받아야 합니다. ③ 의 AppCheck verify 는 verify-only 라 세션토큰을 받지 않습니다.
2. 동일 기기 재로그인
활성(SERVICE_STARTED) 사용자가 같은 디바이스에서 재로그인. 자기 user access token 을 그대로 Authorization 에 싣고, body OTP 는 불필요 (token payload 로 신원 확정). 서버 라우팅 가 user 분기로 라우팅 → resolveReloginIdentity 가 userId 해석 + deviceId/eID 신원 일치를 검증한다 (타인 토큰 + 내 eID 차단).
| # | API | Authorization | body 핵심 | 응답에서 추출할 값 |
|---|---|---|---|---|
| 1 | POST /v2/security/appcheck/verify | Bearer <appToken> (AppCheck 자체는 app token 필요) | { "token", "platform" } | { verified: true } |
| 2 | POST /v2/auth/login/eid/initialize ⭐ | Bearer <userAccessToken> | { "redirectUri" } | authUrl, state |
| 3 | (외부) fbeta OAuth chain | — | — | code, state |
| 4 | POST /v2/auth/login/eid/complete ⭐ | Bearer <userAccessToken> | { "code", "state" } (OTP 없음) | LoginResponseDto |
클라이언트는 보통 app token + user access token 둘 다 보관하고 있으며, AppCheck verify(①) 만 app token, 그 외 로그인 호출은 user access token 으로 보냅니다.
3. 새 기기 첫 로그인
다른 디바이스에서 같은 eID 신원(동일 KVNR)으로 처음 로그인. 새 기기에는 user access token 이 없으므로 새 기기 app token 발급 → LOGIN OTP → AppCheck → fbeta 전체 사전 단계를 거친 뒤 complete 합니다.
| # | API | Authorization | body 핵심 | 응답에서 추출할 값 |
|---|---|---|---|---|
| 1 | POST /v2/auth/app/challenge | 없음 | { "deviceId" (object), "deviceIdHash" } | challenge, nonce |
| 2 | POST /v2/auth/app/complete-challenge | 없음 | { "deviceIdHash", "encryptedChallenge" } | newDeviceAppToken (이후 ③~⑦ Authorization) |
| 3 | POST /v2/auth/email/verification-code | Bearer <newDeviceAppToken> | { "email", "type": "LOGIN" } | requestId |
| 4 | POST /v2/auth/email/verify | Bearer <newDeviceAppToken> | { "email", "code", "requestId", "type": "LOGIN" } | verificationId, lastLoginMethod |
| 5 | POST /v2/security/appcheck/verify | Bearer <newDeviceAppToken> | { "token", "platform" } | { verified: true } |
| 6 | POST /v2/auth/login/eid/initialize ⭐ | Bearer <newDeviceAppToken> | { "redirectUri" } | authUrl, state |
| 7 | (외부) fbeta OAuth chain | — | — | code, state |
| 8 | POST /v2/auth/login/eid/complete ⭐ | Bearer <newDeviceAppToken> | { "code", "state", "email", "emailVerificationId" } | LoginResponseDto |
1 과 차이는 ① ② — 새 기기는 app token 부터 발급받아야 합니다. ⑧ complete 의 핸들러는
(deviceId, email)키로 LOGIN OTP 검증 ID 를 조회하므로 ④ 에서 받은verificationId와 같은
본 endpoint Spec
LI. POST /v2/auth/login/eid/initialize ⭐
1 첫 로그인·2 재로그인·3 새 기기 — 세 경로 모두 같은 endpoint. Authorization 에 담는 토큰 종류만 다르다.
- Path:
POST /v2/auth/login/eid/initialize - 인증: 서버 라우팅 — Authorization Bearer 가 app token 또는 user access token 둘 다 허용
- Rate Limit: 5/분
- 🔗 라이브 명세 (Swagger UI): dev
Request
| Header | Value |
|---|---|
Authorization | 1·3 = Bearer <appToken>, 2 = Bearer <userAccessToken> |
Content-Type | application/json |
{
"redirectUri": "sleepqde://app/oauth2/callback"
}
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
redirectUri | string | Yes | fbeta 인증 완료 후 돌아올 deep link. |
body 에 deviceId 를 추가하면 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"
}
eID 회원가입 1 응답 과 같은 형식. authUrl 은 서버가 PKCE·state·scope 을 모두 채운 완성 URL.
Errors
| HTTP | code | message | 발생 조건 |
|---|---|---|---|
| 400 | 1001 | VALIDATION_ERROR | body 누락/형식 오류 또는 unknown 필드 포함 |
| 401 | 1000 | Authentication required | Authorization 검증 실패 (Authorization 누락, 무효 토큰, 만료) |
| 401 | 1000 | Missing deviceId claim in token payload | token payload 에 deviceId claim 누락 |
| 429 | 1000 | ThrottlerException: Too Many Requests | Endpoint throttle 5/분 |
LC. POST /v2/auth/login/eid/complete ⭐
1·2·3 모두 같은 endpoint. Authorization 토큰 종류와 body 의 email/emailVerificationId 동반 여부로 분기.
- Path:
POST /v2/auth/login/eid/complete - 인증: 서버 라우팅
- Rate Limit: 5/분 (prod 상시 적용; dev/stage/local은
THROTTLE_DISABLED=true설정 시에만 해제) - 🔗 라이브 명세 (Swagger UI): dev
Request
| Header | Value |
|---|---|
Authorization | 1·3 = Bearer <appToken>, 2 = Bearer <userAccessToken> |
Content-Type | application/json |
같은 endpoint 이지만 Authorization 토큰 종류에 따라 body 가 달라진다. code/state 는 항상 필수, email/emailVerificationId 는 app token 분기(1·3 첫 로그인)에서만 필수이고 재로그인(2)에서는 보내지 않는다.
| 필드 | 타입 | 1·3 app token (첫 로그인) | 2 user access token (재로그인) | 설명 |
|---|---|---|---|---|
code | string | 필수 | 필수 | fbeta callback 에서 회수한 OAuth authorization code. |
state | string | 필수 | 필수 | LI 에서 받은 state 그대로. |
email | string | 필수 | 보내지 않음 | 사용자 이메일 (type=LOGIN OTP 로 검증한 값). |
emailVerificationId | string | 필수 | 보내지 않음 | type=LOGIN OTP verify 가 반환한 verificationId. |
재로그인(2)은 token payload 의 userId/deviceId 로 신원이 확정되므로 OTP 동반이 불필요. 첫 로그인(1·3)은 app token 만으로는 사용자를 단정할 수 없어 LOGIN OTP 로 이메일 소유를 추가 입증한다.
Body 예시 — 1 첫 로그인 / 3 새 기기 (app token 분기)
{
"code": "auth_code_from_fbeta",
"state": "abc123xyz...",
"email": "user@example.com",
"emailVerificationId": "verif_uuid_from_login_email_verify"
}
Body 예시 — 2 재로그인 (user access token 분기)
{
"code": "auth_code_from_fbeta",
"state": "abc123xyz..."
}
어느 분기든 body 에 deviceId 가 있으면 400.
Response 200 OK — LoginResponseDto
{
"user": {
"id": "user_uuid",
"email": "user@example.com"
},
"userCycle": {
"id": null,
"status": null
},
"tokens": [
{
"token": "eyJhbGciOi...",
"type": "ACCESS_TOKEN",
"expiresIn": 3600,
"issuedAt": 1730000000
},
{
"token": "eyJhbGciOi...",
"type": "REFRESH_TOKEN",
"expiresIn": 1209600,
"issuedAt": 1730000000
}
],
"roles": [],
"permissions": [],
"profile": {
"userName": "Erika Mustermann",
"language": "de-DE"
},
"cohort": "standard",
"region": "EU",
"sessionPolicy": {
"graceSeconds": 30,
"idleTimeoutMinutes": 30,
"activeTimeoutMinutes": 240
}
}
- 1·3 (first-login):
userCycle.status = null, access token payload 에uci없음 (REGISTERED).POST /v2/auth/user-cycle/activate가uci가 박힌 새 토큰 재발급. - 2 (재로그인):
userCycle.status = "ACTIVE", access token payload 에uci포함.
Errors
| HTTP | code | message | 발생 조건 |
|---|---|---|---|
| 400 | 1001 | VALIDATION_ERROR | body 필드 누락/형식 오류 또는 unknown 필드 포함 |
| 400 | 2008 | EMAIL_NOT_VERIFIED | 1·3 에서 LOGIN OTP 검증 ID 불일치/만료 또는 누락 |
| 400 | 2223 | EID_INVALID_CODE | fbeta authorization code 무효 또는 토큰 교환 실패 |
| 400 | 2224 | EID_STATE_MISMATCH | OAuth state 불일치/만료 |
| 401 | 1000 | Authentication required | Authorization 검증 실패 |
| 404 | 2222 | EID_NOT_FOUND | 교환된 eID 해시에 매칭되는 활성 EidLink 없음 (2·3) |
| 429 | 1000 | ThrottlerException: Too Many Requests | Endpoint throttle 5/분 (prod 상시 적용) |
다음 단계
- 1 첫 로그인 직후 → 서비스 활성화:
POST /v2/auth/user-cycle/activate(Authorization=user access token). - 2·3 후 → 일반 보호 endpoint 호출.
- Native 2FA 로그인 (디바이스 키 서명 기반, eID 와 대칭): Native 2FA 로그인.
- 마스터 문서: Auth 도메인 endpoints (로그인).