본문으로 건너뛰기

Eid 명세

개요​

이 문서는 독일 Eid(건강ID) 시스템과의 연동을 위한 기술적 명세를 정의합니다. Eid는 독일의 디지털 의료 생태계에서 표준화된 사용자 인증 및 본인 확인 시스템으로, OpenID Connect 프로토콜을 기반으로 구현되어 있습니다.

Eid 시스템 개요​

Eid는 다음과 같은 주요 기능을 제공합니다:

  1. 사용자 인증 (Authentication)
  2. 본인 확인 (Identity Verification)
  3. 속성 제공 (Attribute Provision)
  4. 동의 관리 (Consent Management)

OpenID Connect 프로토콜​

Eid는 OpenID Connect 1.0 프로토콜을 구현하고 있으며, 다음과 같은 흐름을 지원합니다:

  1. 인증 코드 흐름 (Authorization Code Flow)
  2. 암시적 흐름 (Implicit Flow)
  3. 하이브리드 흐름 (Hybrid Flow)

당사 시스템에서는 보안상의 이유로 인증 코드 흐름만 사용합니다.

엔드포인트 명세​

디스커버리 엔드포인트​

GET https://eid.example.de/.well-known/openid-configuration

이 엔드포인트를 통해 Eid 서비스의 엔드포인트 정보를 자동으로 조회할 수 있습니다.

인증 엔드포인트​

GET https://eid.example.de/auth

이 엔드포인트는 사용자를 Eid 인증 페이지로 리다이렉트하는 데 사용됩니다.

토큰 엔드포인트​

POST https://eid.example.de/token

이 엔드포인트는 인증 코드를 액세스 토큰, ID 토큰, 리프레시 토큰으로 교환하는 데 사용됩니다.

사용자 정보 엔드포인트​

GET https://eid.example.de/userinfo

이 엔드포인트는 사용자 정보를 조회하는 데 사용됩니다.

JWK 세트 엔드포인트​

GET https://eid.example.de/jwks

이 엔드포인트는 ID 토큰 검증에 사용되는 공개 키를 조회하는 데 사용됩니다.

클라이언트 등록​

Eid 시스템과 연동하기 위해서는 사전에 클라이언트 등록이 필요합니다. 등록 시 다음 정보가 필요합니다:

  1. 클라이언트 이름
  2. 리다이렉트 URI
  3. 로고 이미지
  4. 개인정보처리방침 URL
  5. 요청 스코프

등록이 완료되면 다음 정보가 발급됩니다:

  1. 클라이언트 ID
  2. 클라이언트 시크릿

인증 요청 파라미터​

Eid 인증 요청 시 다음 파라미터를 포함해야 합니다:

파라미터설명필수 여부
client_id클라이언트 ID필수
redirect_uri인증 완료 후 리다이렉트할 URI필수
response_type'code' (인증 코드 흐름)필수
scope요청 스코프 (최소 'openid')필수
stateCSRF 방지를 위한 상태값필수
nonce재생 공격 방지를 위한 임의 값필수
acr_values인증 컨텍스트 참조 값선택
prompt인증 프롬프트 동작 ('none', 'login', 'consent')선택
max_age최대 인증 기간 (초)선택
ui_localesUI 언어 설정 (예: 'de', 'en')선택

토큰 응답 형식​

토큰 엔드포인트 응답은 다음과 같은 형식입니다:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}

ID 토큰 형식​

ID 토큰은 JWT(JSON Web Token) 형식으로, 다음과 같은 클레임을 포함합니다:

클레임설명필수 여부
iss발급자 (issuer) - Eid 인증 서버 URL필수
sub주체 (subject) - 사용자의 Eid필수
aud대상 (audience) - 클라이언트 ID필수
exp만료 시간 (expiration time)필수
iat발급 시간 (issued at)필수
auth_time사용자 인증 시간필수
nonce요청 시 전달한 nonce 값필수
acr인증 컨텍스트 참조 값조건부
amr인증 방법 참조 값조건부
azp권한 부여 당사자조건부
name사용자 이름선택
given_name사용자 이름 (first name)선택
family_name사용자 성 (last name)선택
email사용자 이메일선택
email_verified이메일 검증 여부선택
birthdate생년월일선택

지원 스코프​

Eid는 다음 스코프를 지원합니다:

스코프설명
openidOpenID Connect 기본 스코프 (필수)
profile사용자 프로필 정보 (이름, 성별, 생년월일 등)
email사용자 이메일 주소
phone사용자 전화번호
address사용자 주소
health.insurance사용자 건강보험 정보

오류 코드​

Eid 인증 및 토큰 요청 시 발생할 수 있는 주요 오류 코드:

오류 코드설명
invalid_request잘못된 요청 파라미터
unauthorized_client승인되지 않은 클라이언트
access_denied사용자가 접근을 거부함
unsupported_response_type지원하지 않는 응답 타입
invalid_scope잘못된 스코프
server_error서버 오류
temporarily_unavailable일시적으로 서비스 사용 불가
invalid_grant잘못된 인증 코드 또는 리프레시 토큰
invalid_client클라이언트 인증 실패

보안 고려사항​

인증 코드 흐름​

인증 코드 흐름은 다음과 같은 이유로 가장 안전한 방식입니다:

  1. 클라이언트 시크릿이 백엔드에서만 사용됨
  2. 액세스 토큰이 프론트엔드로 노출되지 않음
  3. PKCE(Proof Key for Code Exchange) 확장을 지원하여 추가 보안 제공

PKCE 구현​

PKCE를 구현하려면 다음 단계를 따라야 합니다:

  1. 코드 인증(code_verifier) 생성: 최소 43자 이상의 무작위 문자열
  2. 코드 챌린지(code_challenge) 생성: code_verifier를 SHA-256으로 해싱하고 Base64-URL로 인코딩
  3. 인증 요청 시 code_challenge 및 code_challenge_method 파라미터 포함
  4. 토큰 요청 시 code_verifier 파라미터 포함

토큰 검증​

ID 토큰 검증 시 다음 사항을 확인해야 합니다:

  1. 서명 검증 (JWK 세트 엔드포인트에서 공개 키 조회)
  2. 발급자(iss) 검증
  3. 대상(aud) 검증
  4. 만료 시간(exp) 검증
  5. 발급 시간(iat) 검증
  6. nonce 검증

구현 예시​

인증 요청 URL 생성​

function generateAuthUrl(state: string, nonce: string): string {
const params = new URLSearchParams({
client_id: 'YOUR_CLIENT_ID',
redirect_uri: 'YOUR_REDIRECT_URI',
response_type: 'code',
scope: 'openid profile email',
state,
nonce,
prompt: 'consent'
});

return `https://eid.example.de/auth?${params.toString()}`;
}

토큰 교환​

async function exchangeCodeForTokens(code: string, state: string, nonce: string): Promise<TokenSet> {
const response = await fetch('https://eid.example.de/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: 'YOUR_REDIRECT_URI'
}).toString()
});

if (!response.ok) {
throw new Error('Token exchange failed');
}

const tokenSet = await response.json();

// ID 토큰 검증
validateIdToken(tokenSet.id_token, nonce);

return tokenSet;
}

ID 토큰 검증​

async function validateIdToken(idToken: string, expectedNonce: string): Promise<void> {
// JWK 세트 가져오기
const jwksResponse = await fetch('https://eid.example.de/jwks');
const jwks = await jwksResponse.json();

// 토큰 디코딩 및 검증
const decodedToken = jwt.decode(idToken, { complete: true });
const kid = decodedToken.header.kid;

// 키 찾기
const key = jwks.keys.find(k => k.kid === kid);
if (!key) {
throw new Error('Signing key not found');
}

// 서명 검증
const isValid = jwt.verify(idToken, key, {
algorithms: ['RS256'],
audience: CLIENT_ID,
issuer: 'https://eid.example.de'
});

if (!isValid) {
throw new Error('ID token validation failed');
}

const payload = jwt.decode(idToken);

// nonce 검증
if (payload.nonce !== expectedNonce) {
throw new Error('Invalid nonce');
}

// 만료 시간 검증
if (Date.now() / 1000 > payload.exp) {
throw new Error('Token expired');
}
}