Appearance
REST API
통합회원 REST API 의 엔드포인트별 요청과 응답을 설명합니다. 예제는 실제 서버 응답입니다. OpenID Connect 라이브러리를 사용하면 대부분의 API 를 직접 호출하지 않습니다.
상태 확정 · 최종 확인 2026-09-30
시작하기 전에
로그인 과정
- 연동 서비스가 사용자 브라우저를 인가 코드 요청 URL 로 이동시킵니다.
- 사용자가 통합회원 화면에서 로그인하고, 최초 연결 시 제3자 제공 동의를 완료합니다.
- 통합회원이
redirect_uri로 인가 코드를 전달합니다. - 연동 서비스 서버가 인가 코드로 토큰 요청을 수행하고, ID 토큰을 검증합니다.
- 연동 서비스가 ID 토큰의
sub로 회원을 조회하거나 등록하고, 연동 서비스 세션을 발급합니다.
공통 사항
- 모든 URL 은 호스트를 기준으로 합니다. 호스트는 이해하기 › 도입 안내를 참고하시기 바랍니다.
- 토큰, 사용자 정보, 토큰 폐기 엔드포인트는 서버 간 호출 전용입니다. CORS 를 허용하지 않으므로 브라우저에서 호출할 수 없습니다.
- 파라미터 표의 필수 여부는 연동 서비스가 지켜야 하는 기준입니다. 서버가 누락을 거부하는 파라미터는
client_id,redirect_uri,response_type,scope, PKCE 이며,state와nonce는 누락되어도 거부하지 않습니다. 두 값을 보내지 않으면 CSRF 와 재사용 방지가 동작하지 않습니다. - 리다이렉트 응답의 상태 코드는 303 입니다.
| 항목 | 고정값 |
|---|---|
| 인가 방식 | Authorization Code + PKCE(S256) |
response_type | code |
| 클라이언트 인증 | client_secret_post (요청 본문). HTTP Basic 헤더는 지원하지 않습니다 |
| scope | openid, email, offline_access |
| 회원번호 | pairwise sub |
| 서명 | RS256. JWKS 로 공개키를 게시하며 kid 로 키를 선택합니다 |
| 시각 허용 오차 | exp +60초, iat −60초 |
디스커버리
서버 설정 정보를 JSON 으로 제공합니다. 라이브러리는 이 주소로 엔드포인트와 서명키 위치를 조회합니다.
| 메서드 | URL | 인증 방식 |
|---|---|---|
| GET | /.well-known/openid-configuration | 없음 |
응답
json
{
"issuer": "https://membership.m-box.com",
"authorization_endpoint": "https://membership.m-box.com/oauth/authorize",
"token_endpoint": "https://membership.m-box.com/oauth/token",
"userinfo_endpoint": "https://membership.m-box.com/oauth/userinfo",
"jwks_uri": "https://membership.m-box.com/oauth/jwks",
"end_session_endpoint": "https://membership.m-box.com/oauth/logout",
"revocation_endpoint": "https://membership.m-box.com/oauth/revoke",
"scopes_supported": ["openid", "email", "offline_access"],
"claims_supported": ["sub", "email", "email_verified", "sid", "auth_time", "iss"],
"claim_types_supported": ["normal"],
"response_types_supported": ["code"],
"response_modes_supported": ["form_post", "fragment", "query"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"subject_types_supported": ["pairwise"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["client_secret_post"],
"id_token_signing_alg_values_supported": ["RS256"],
"authorization_response_iss_parameter_supported": true,
"claims_parameter_supported": false,
"request_parameter_supported": false,
"request_uri_parameter_supported": false
}NOTE
디스커버리 항목 참고
- PAR(
pushed_authorization_request_endpoint)은 지원하지 않으며 디스커버리에 포함되지 않습니다. token_endpoint_auth_methods_supported는client_secret_post뿐입니다. HTTP Basic 헤더(client_secret_basic)로 요청하면 401invalid_client가 반환됩니다.response_mode는 지정하지 않으며 기본값(query)만 사용합니다.sid는 백채널 로그아웃을 제공하지 않으므로 사용하지 않습니다.aud,exp,iat,nonce,at_hash는 목록에 없으나 ID 토큰에 항상 포함됩니다.
JWKS
ID 토큰 서명 검증용 공개키 목록입니다. 키는 kid 로 구분합니다. 캐시하여 사용하되, 토큰 헤더의 kid 가 목록에 없으면 다시 조회해야 합니다(키 교체).
| 메서드 | URL | 인증 방식 |
|---|---|---|
| GET | /oauth/jwks | 없음 |
응답
json
{ "keys": [ { "kty": "RSA", "kid": "k-2026-09-23-db08ab", "alg": "RS256", "use": "sig", "n": "…", "e": "AQAB" } ] }인가 코드 요청
사용자에게 로그인 화면(최초 연결 시 동의 화면 포함)을 표시하고, 완료되면 redirect_uri 로 인가 코드를 전달합니다. 사용자 브라우저를 이 URL 로 이동시키며, 전체 페이지 이동으로 호출해야 합니다(iframe 차단).
| 메서드 | URL | 인증 방식 | 요구 사항 |
|---|---|---|---|
| GET | /oauth/authorize | 없음 | 클라이언트 등록, redirect_uri 등록 |
요청 — 쿼리 파라미터
| 이름 | 타입 | 설명 | 필수 |
|---|---|---|---|
client_id | String | 발급받은 클라이언트 ID | O |
redirect_uri | String | 등록한 값과 정확히 일치하는 주소. URL 인코딩 | O |
response_type | String | code 고정 | O |
scope | String | 공백으로 구분하며 openid 는 필수입니다. 예: openid email. 지원하지 않는 scope 는 오류 없이 무시됩니다 | O |
state | String | CSRF 방지용 임의값(32자 이상 권장). 콜백에 그대로 전달됩니다 | O |
nonce | String | ID 토큰 재사용 방지용 임의값. ID 토큰의 nonce 로 전달됩니다 | O |
code_challenge | String | BASE64URL(SHA256(code_verifier)). code_verifier 는 43~128자 임의 문자열이며 세션에 보관합니다 | O |
code_challenge_method | String | S256 고정 | O |
prompt | String | login: 통합회원 세션이 있어도 재인증 · consent: 동의 화면 표시(offline_access 요청 시 필수, 이미 동의한 사용자에게도 매번 표시) · create: login_hint 가 있으면 기존 회원 전환 화면(활용하기 › 기존 회원 연결), 없으면 가입 화면. 통합회원 세션이 있으면 계정 확인 화면을 먼저 표시 · none: 화면 없이 처리(세션이 없으면 login_required, 동의가 필요하면 consent_required). 권장하지 않으며, 다른 값과 함께 사용하면 invalid_request | X |
login_hint | String | 로그인 · 가입 화면의 이메일 입력란에 미리 채울 값 | X |
max_age | Integer | 마지막 인증 후 허용 시간(초). 초과 시 재인증하며, ID 토큰에 auth_time 이 포함됩니다 | X |
응답 — redirect_uri 쿼리 파라미터
| 이름 | 타입 | 설명 | 필수 |
|---|---|---|---|
code | String | 인가 코드. 60초 안에 한 번 교환할 수 있습니다. 교환에 실패한 경우(잘못된 code_verifier 등) 코드가 소모되지 않으므로 60초 안에 다시 요청할 수 있습니다 | 성공 시 O |
state | String | 요청 시 전달한 값. 일치하지 않으면 처리를 중단해야 합니다 | O |
iss | String | 발급자. 디스커버리의 issuer 와 비교합니다(RFC 9207) | O |
error | String | 오류 코드. 문제 해결 › 오류 코드 | 실패 시 O |
error_description | String | 개발자용 설명. 서버 기술 오류는 영문, 사용자 행위(취소 등)는 한글입니다. 문구는 변경될 수 있으므로 분기에는 error 를 사용해야 합니다 | 실패 시 O |
response_type 오류는 쿼리가 아닌 fragment(#error=unsupported_response_type…)로 전달됩니다. redirect_uri 나 client_id 가 잘못되어 리다이렉트할 수 없는 오류는 통합회원 오류 화면에 표시됩니다.
예제
GET /oauth/authorize
?client_id=remit-web
&redirect_uri=https%3A%2F%2Fwww.remit.example%2Fcb
&response_type=code
&scope=openid%20email
&state=Sau634G8avjmwmDjVcNRAKQO5dQ3UTKR
&nonce=7hAWqHGxvf7iTYF8GLA-Ue5PTHFnLao0
&code_challenge=tXEQEahZ3p-BDun7VtJuzJ9K7kcvpUMhjhilB-azuio
&code_challenge_method=S256성공: https://www.remit.example/cb?code=vgmyQYwAVSz4hZc7lleeZ9ecfNI2iI_Ha-JKpO2ZzzQ&state=Sau634G8…&iss=https%3A%2F%2Fmembership.m-box.com
취소: https://www.remit.example/cb?error=access_denied&error_description=%EC%82%AC%EC%9A%A9%EC%9E%90%EA%B0%80+%EC%B7%A8%EC%86%8C%ED%96%88%EC%8A%B5%EB%8B%88%EB%8B%A4&state=Sau634G8…&iss=… (「사용자가 취소했습니다」)
PKCE 누락: https://www.remit.example/cb?error=invalid_request&error_description=Authorization+Server+policy+requires+PKCE+to+be+used+for+this+request&state=…&iss=…토큰 요청
인가 코드로 ID 토큰과 액세스 토큰을 발급받습니다. 시크릿이 필요하므로 연동 서비스 서버에서 호출해야 합니다.
| 메서드 | URL | 인증 방식 | 요구 사항 |
|---|---|---|---|
| POST | /oauth/token | client_secret_post — 요청 본문에 client_id · client_secret 포함. HTTP Basic 헤더는 지원하지 않습니다(401 invalid_client) | 인가 코드 |
요청 — 헤더
| 이름 | 값 | 필수 |
|---|---|---|
Content-Type | application/x-www-form-urlencoded | O |
요청 — 본문
| 이름 | 타입 | 설명 | 필수 |
|---|---|---|---|
grant_type | String | authorization_code 고정 | O |
code | String | 인가 코드 | O |
redirect_uri | String | 인가 요청에 사용한 값 | O |
client_id | String | 클라이언트 ID | O |
client_secret | String | 클라이언트 시크릿 | O |
code_verifier | String | code_challenge 생성에 사용한 원본 값 | O |
응답 — 본문
| 이름 | 타입 | 설명 | 필수 |
|---|---|---|---|
id_token | String | 사용자 정보가 담긴 JWT. 검증 후 사용해야 합니다 | O |
access_token | String | 사용자 정보 조회용 | O |
token_type | String | Bearer | O |
expires_in | Integer | 액세스 토큰 유효 시간(초). 600 | O |
scope | String | 실제로 부여된 scope. 무시된 항목은 포함되지 않습니다 | O |
refresh_token | String | offline_access 와 prompt=consent 를 함께 요청한 경우에만 발급 | X |
ID 토큰 페이로드
| 클레임 | 설명 | 항상 제공 여부 |
|---|---|---|
iss | 발급자. 디스커버리의 issuer 와 일치해야 합니다 | 예 |
aud | 연동 서비스의 client_id 와 일치해야 합니다 | 예 |
sub | 서비스별 회원번호 | 예 |
email email_verified | email scope 요청 시 포함 | 아니오 |
nonce | 인가 요청의 nonce 와 일치해야 합니다 | 예 |
iat exp | 발급 · 만료 시각(Unix 초). exp 가 지났으면 거부해야 합니다 | 예 |
at_hash | 함께 발급된 액세스 토큰의 해시. 라이브러리가 검증합니다 | 예 |
auth_time | 실제 인증 시각. prompt=login 또는 max_age 요청 시에만 포함 | 아니오 |
검증 순서는 헤더의 kid 로 JWKS 에서 키 선택, RS256 서명 확인, iss · aud · exp · nonce 확인입니다. 라이브러리의 ID 토큰 검증 기능이 이를 모두 수행합니다. aud 는 단일 문자열이며 azp 는 없습니다. 시각 허용 오차는 exp 현재 시각 +60초, iat 현재 시각 −60초입니다. 직접 구현하는 절차는 활용하기 › ID 토큰 검증을 참고하시기 바랍니다.
예제
bash
curl -X POST "https://membership.m-box.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=vgmyQYwAVSz4hZc7lleeZ9ecfNI2iI_Ha-JKpO2ZzzQ" \
-d "redirect_uri=https://www.remit.example/cb" \
-d "client_id=remit-web" \
-d "client_secret=${CLIENT_SECRET}" \
-d "code_verifier=${CODE_VERIFIER}"json
{
"access_token": "fHh6IXkn_hauA14JsYGzs4eQmS0y3lnwe9wfMhxQZzL",
"expires_in": 600,
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImstMjAyNi0wOS0yMy1kYjA4YWIifQ.eyJzdWIiOi…",
"scope": "openid email",
"token_type": "Bearer"
}ID 토큰 헤더 · 페이로드(디코드):
json
{ "alg": "RS256", "typ": "JWT", "kid": "k-2026-09-23-db08ab" }json
{
"sub": "rm_7996cf354b131e55b31f262540e4c50f",
"email": "hong@example.com",
"email_verified": true,
"nonce": "7hAWqHGxvf7iTYF8GLA-Ue5PTHFnLao0",
"at_hash": "LxK3KvwgAKqAHoh0a5a0sg",
"aud": "remit-web",
"exp": 1790149873,
"iat": 1790149273,
"iss": "https://membership.m-box.com"
}오류 — HTTP 400/401, 본문 {"error": "…", "error_description": "…"}
| HTTP | error | error_description | 원인 |
|---|---|---|---|
| 400 | invalid_grant | grant request is invalid | 코드 만료(60초) · 코드 재사용 · code_verifier 불일치 · redirect_uri 불일치 |
| 401 | invalid_client | client authentication failed | client_id · client_secret 오류 |
| 401 | invalid_client | client_secret_basic 은 지원하지 않는다. client_id 와 client_secret 을 요청 본문에 넣어(client_secret_post) 보낼 것. | Authorization: Basic 헤더로 요청한 경우. 요청 본문 방식(client_secret_post)만 지원합니다. 인가 코드는 소모되지 않으므로 방식을 수정하여 같은 코드로 다시 요청할 수 있습니다 |
| 400 | invalid_request | (누락 항목 설명) | 필수 파라미터 누락 |
토큰 폐기 엔드포인트(/oauth/revoke)도 Basic 헤더로 요청하면 401 invalid_client 를 반환합니다.
토큰 갱신
리프레시 토큰으로 새 액세스 토큰과 ID 토큰을 발급받습니다. 리프레시 토큰도 새 값으로 교체되며 기존 값은 즉시 무효가 되므로, 응답의 새 값을 저장해야 합니다.
| 메서드 | URL | 인증 방식 | 요구 사항 |
|---|---|---|---|
| POST | /oauth/token | client_secret_post | offline_access scope 로 발급된 리프레시 토큰 |
요청 — 본문
| 이름 | 타입 | 설명 | 필수 |
|---|---|---|---|
grant_type | String | refresh_token 고정 | O |
refresh_token | String | 현재 리프레시 토큰 | O |
client_id | String | 클라이언트 ID | O |
client_secret | String | 클라이언트 시크릿 | O |
응답 — 본문: 토큰 요청과 같은 구조(access_token, expires_in, id_token, refresh_token, scope, token_type)이며, refresh_token 에 새 값이 전달됩니다.
갱신 응답의 id_token 에는 최초 인가 요청의 nonce 가 포함됩니다. 갱신 시에는 nonce 를 검증하지 않으며(표준), 서명 · iss · aud · exp 와 sub 가 최초와 같은지 확인합니다.
오류
| HTTP | error | 원인 | 조치 |
|---|---|---|---|
| 400 | invalid_grant (grant request is invalid) | 토큰 만료(30일), 이미 교체된 토큰, 계정 정지 · 탈퇴, 사용자의 연결 해제(동의 철회) | 재시도하지 않고 토큰을 폐기한 뒤 재로그인을 안내합니다 |
사용자 정보 조회
액세스 토큰으로 사용자 정보를 조회합니다. ID 토큰에 같은 정보가 포함되어 있으므로 일반적으로 호출할 필요가 없습니다.
| 메서드 | URL | 인증 방식 |
|---|---|---|
| GET | /oauth/userinfo | 액세스 토큰 (Bearer) |
요청 — 헤더
| 이름 | 값 | 필수 |
|---|---|---|
Authorization | Bearer ${ACCESS_TOKEN} | O |
응답 — 본문
| 이름 | 타입 | 설명 | 필수 |
|---|---|---|---|
sub | String | 서비스별 회원번호 | O |
email | String | 이메일 | email scope 요청 시 |
email_verified | Boolean | 이메일 확인 여부 | email scope 요청 시 |
예제
bash
curl "https://membership.m-box.com/oauth/userinfo" -H "Authorization: Bearer ${ACCESS_TOKEN}"json
{ "sub": "rm_7996cf354b131e55b31f262540e4c50f", "email": "hong@example.com", "email_verified": true }오류
| HTTP | error | 원인 |
|---|---|---|
| 400 | invalid_request — no access token provided (+ WWW-Authenticate: Bearer realm="…") | Authorization 헤더 누락 |
| 401 | invalid_token — invalid token provided | 만료 · 폐기된 액세스 토큰 |
토큰 폐기
액세스 토큰 또는 리프레시 토큰을 즉시 무효화합니다. 통합회원 로그아웃(/oauth/logout)을 요청하면 해당 서비스의 토큰은 통합회원이 함께 폐기하므로, 이 엔드포인트는 연동 서비스만 로그아웃할 때 사용합니다.
| 메서드 | URL | 인증 방식 |
|---|---|---|
| POST | /oauth/revoke | client_secret_post |
요청 — 본문
| 이름 | 타입 | 설명 | 필수 |
|---|---|---|---|
token | String | 폐기할 토큰 | O |
token_type_hint | String | access_token 또는 refresh_token | X |
client_id client_secret | String | 클라이언트 인증 | O |
응답: 200, 본문 없음. 이미 무효인 토큰도 200 을 반환합니다. 폐기된 액세스 토큰으로 사용자 정보를 조회하면 401 invalid_token 이 반환됩니다.
로그아웃
통합회원 세션을 종료합니다. 사용자 브라우저를 이 URL 로 이동시키면 확인 화면이 표시되며, 사용자가 로그아웃을 선택하면 세션이 종료됩니다.
| 메서드 | URL | 인증 방식 |
|---|---|---|
| GET | /oauth/logout | 없음 (브라우저 세션) |
요청 — 쿼리 파라미터
| 이름 | 타입 | 설명 | 필수 |
|---|---|---|---|
id_token_hint | String | 로그인 시 발급받은 ID 토큰. 요청한 클라이언트를 식별합니다 | 권장 |
client_id | String | id_token_hint 를 보내지 않는 경우 클라이언트 식별용. 둘 중 하나는 전달해야 합니다 | X |
post_logout_redirect_uri | String | 로그아웃 후 이동할 주소. 등록된 값만 허용됩니다(설정하기 › 로그아웃 리다이렉트 URI) | X |
state | String | 복귀 주소로 그대로 전달됩니다 | X |
확인 화면 — 제목은 "MBOX 통합회원에서 로그아웃할까요?"이며, 본문에 요청한 서비스 이름과 로그아웃 · 로그인 유지 버튼이 표시됩니다. id_token_hint 와 client_id 가 모두 없으면 서비스 이름 대신 "이용 중인 서비스"가 표시됩니다.
결과
| 상황 | 결과 |
|---|---|
로그아웃 + 등록된 post_logout_redirect_uri | 통합회원 세션이 종료되고 해당 주소로 303 리다이렉트됩니다. state 를 전달한 경우 ?state=… 로 함께 전달됩니다 |
| 로그아웃 + 복귀 주소 없음 | 통합회원 세션이 종료되고 완료 화면("로그아웃되었습니다")이 표시됩니다 |
| 로그인 유지 | 통합회원 세션이 유지됩니다. 복귀 주소를 전달한 경우 로그아웃과 같은 방식으로 state 와 함께 303 리다이렉트됩니다. 이후 인가 요청은 화면 없이 처리되며, 요청한 서비스의 토큰은 로그아웃과 마찬가지로 폐기됩니다 |
등록되지 않은 post_logout_redirect_uri | 연동 서비스로 이동하지 않고, 400 과 함께 통합회원 오류 화면(invalid_request: post_logout_redirect_uri not registered)이 표시됩니다 |
WARNING
복귀 여부로 로그아웃을 판단할 수 없습니다
로그아웃과 로그인 유지의 복귀 응답은 상태 코드, 복귀 주소, state 값이 모두 같으며, 결과를 구분하는 파라미터가 없습니다. 따라서 연동 서비스 세션은 사용자를 /oauth/logout 으로 이동시키기 전에 삭제해야 합니다.
로그아웃을 요청한 서비스의 액세스 · 리프레시 토큰은 두 경우 모두 통합회원이 폐기합니다(offline_access 로 발급된 토큰 포함). 복귀 후 해당 토큰으로 갱신하면 invalid_grant, 사용자 정보 조회는 invalid_token 이 반환됩니다. 다른 서비스의 토큰에는 영향이 없습니다.
이 API 는 통합회원 세션만 종료합니다. 각 서비스의 세션은 각 서비스가 종료해야 합니다(백채널 로그아웃 미제공).