Developer Portal
Portlogics ID 연동 가이드
사내 서비스와 MCP 서버가 Portlogics 통합 인증을 사용하도록 연동하는 방법을 안내합니다. OAuth 2.1 / OpenID Connect 및 MCP 2025-11-25 명세를 따릅니다.
소개
Portlogics ID 는 Portlogics 사내 서비스의 통합 인증 플랫폼입니다.
- ID Token 식별자: 불변
subUUID. 이메일 매칭 금지 — 사용자의 이메일은 바뀔 수 있습니다 - 지원 Grant: authorization code + PKCE, refresh_token, device_code
- access_token: JWT (RS256). 사용자 속성은
ext네임스페이스의 email / name / email_verified / affiliation 에 실려 있어/userinforound-trip 없이 1회 서명 검증으로 식별 가능합니다
빠른 시작
- 클라이언트 등록 — 내 클라이언트 페이지 에서 사내 구성원이 직접 등록 (Portlogics 로그인 필요). 사내 전용 RP 는
requires_affiliation체크박스로 외부 사용자 차단 - Discovery 문서 확인 —
/.well-known/openid-configuration을 호출해 authorize / token / userinfo / jwks URL 을 동적으로 로드하세요 - Authorize 리다이렉트 —
code_challenge(PKCE) 포함하여/oauth2/auth?response_type=code&client_id=...로 사용자를 리다이렉트 - Callback 에서 code 수령 → token 교환 —
code_verifier와 함께POST /oauth2/token호출.id_token과access_token수신 - 사용자 식별자 저장 —
id_token의sub(UUID) 를 RP DB 의 사용자 키로 저장.email/name은 표시용, 식별용 아님
엔드포인트
Base URL 은 https://id.portlogics.kr 입니다. 모든 엔드포인트는 Discovery 문서에서 동적으로 읽어오는 것을 권장합니다 — 경로/정책이 변경되어도 RP 코드 수정이 필요 없습니다.
| 목적 | 경로 |
|---|---|
| OpenID Discovery | /.well-known/openid-configuration |
| OAuth 2.0 AS Metadata (RFC 8414) | /.well-known/oauth-authorization-server |
| JSON Web Key Set | /.well-known/jwks.json |
| Authorization | /oauth2/auth |
| Token 교환 / 갱신 | /oauth2/token |
| Userinfo | /userinfo |
| Revocation | /oauth2/revoke |
| Dynamic Client Registration (DCR) | /oauth2/register |
클라이언트 등록
Portlogics 사내 구성원은 내 클라이언트 페이지 에서 OAuth 클라이언트를 직접 등록 · 관리할 수 있습니다. 로그인 후 접근하면 이 계정이 소유한 클라이언트 목록이 보이고, 언제든 새로 만들거나 시크릿을 회전하거나 삭제할 수 있습니다.
등록 시 설정 항목
- 이름 — 사용자 동의 화면에 표시되고, 관리자가 이 앱을 식별하는 데 쓰입니다
- Redirect URIs — OAuth 완료 후 브라우저가 돌아올 URL. 정확히 일치해야 하며 와일드카드 불가. HTTPS 권장(로컬 개발의
localhost는 HTTP 허용) - Scope —
openid/email/profile/offline_access중 필요한 것. 이 4개로 고정되어 있으며 커스텀 scope 은 추가할 수 없습니다 - Audience — MCP 서버 같이 토큰을 수신할 리소스 URL. 일반 RP 는 비워두세요 (RFC 8707)
- 사내 구성원 전용 — 체크하면 Portlogics 구성원만 이 앱에 로그인할 수 있습니다. 외부 사용자는 consent 단계에서 거부됩니다
셀프서비스 제한
모든 클라이언트는 confidential 타입으로 등록됩니다 (서버 사이드에서 시크릿을 안전히 보관할 수 있어야 함). 다음 경우에는 관리자(happy@portlogics.com) 에게 요청해 주세요:
- SPA · 모바일 네이티브 등 public 클라이언트 (시크릿 없이 PKCE 만 사용)
- Claude Desktop · claude.ai 같이 여러 사용자가 공유 하는 호스팅 클라이언트
- 외부 파트너와 공유하는 통합 클라이언트
스코프 & 클레임
각 스코프 요청 시 id_token 및 access_token (ext 클레임) 에 포함되는 값입니다.
| Scope | 클레임 | 설명 |
|---|---|---|
openid | sub | 필수. 사용자의 불변 UUID 식별자 |
email | email, email_verified | 이메일 및 검증 여부 (표시용) |
profile | name, affiliation | 사용자 이름 + 소속 구분 (portlogics_member / external) |
offline_access | — | refresh_token 발급. 장기 세션 필요 시 |
Auth Code + PKCE 예시
플로우 개요
User → RP(/login) → Portlogics ID(/oauth2/auth?code_challenge=...)
→ 사용자 로그인 + 동의
→ RP callback(?code=...)
→ POST /oauth2/token (code + code_verifier)
→ id_token + access_token + (refresh_token)TypeScript 예시
// 1) authorize 리다이렉트 URL 생성
const verifier = randomUrlSafe(64);
const challenge = base64url(sha256(verifier));
const authorizeUrl = new URL("https://id.portlogics.kr/oauth2/auth");
authorizeUrl.searchParams.set("response_type", "code");
authorizeUrl.searchParams.set("client_id", "_internal.my-app");
authorizeUrl.searchParams.set("redirect_uri", "https://my-app.portlogics.kr/cb");
authorizeUrl.searchParams.set("scope", "openid email profile offline_access");
authorizeUrl.searchParams.set("state", randomUrlSafe(16));
authorizeUrl.searchParams.set("nonce", randomUrlSafe(16));
authorizeUrl.searchParams.set("code_challenge", challenge);
authorizeUrl.searchParams.set("code_challenge_method", "S256");
// 2) callback 에서 code 수령 후 token 교환
const tokenResp = await fetch("https://id.portlogics.kr/oauth2/token", {
method: "POST",
headers: {
"content-type": "application/x-www-form-urlencoded",
authorization: "Basic " + btoa(clientId + ":" + clientSecret),
},
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: "https://my-app.portlogics.kr/cb",
code_verifier: verifier,
}),
});
const tokens = await tokenResp.json();
// tokens.id_token, tokens.access_token, tokens.refresh_tokencurl 로 token 교환
curl -u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=authorization_code" \
-d "code=$CODE" \
-d "redirect_uri=https://my-app.portlogics.kr/cb" \
-d "code_verifier=$VERIFIER" \
https://id.portlogics.kr/oauth2/tokenMCP 서버 연동
MCP 서버는 Portlogics ID 발급 access_token (JWT) 을 검증해 사용자 식별자를 얻습니다. /userinfo round-trip 없이 JWKS 로 서명만 확인하면 충분합니다.
JWT 검증 코드 (jose)
import { createRemoteJWKSet, jwtVerify } from "jose";
const ISSUER = "https://id.portlogics.kr";
const JWKS = createRemoteJWKSet(
new URL(ISSUER + "/.well-known/jwks.json"),
);
async function verify(bearer: string) {
const { payload } = await jwtVerify(bearer, JWKS, {
issuer: ISSUER,
audience: "https://my-mcp.portlogics.kr", // client 등록 시 바인딩한 값
});
// payload.sub → 사용자 UUID (식별자)
// payload.ext.email → 표시용 이메일
// payload.ext.affiliation → "portlogics_member" 또는 "external"
return payload;
}이중 방어: 서버 측 affiliation 게이트
IdP 쪽 consent 게이트만으로는 충분하지 않습니다. RP/MCP 서버도 같은 조건을 재검증해 좀비 토큰이나 misconfiguration 을 차단하세요.
const ALLOWED_AFFILIATIONS = ["portlogics_member"];
const affiliation = (payload.ext as { affiliation?: string })?.affiliation;
if (!affiliation || !ALLOWED_AFFILIATIONS.includes(affiliation)) {
return new Response("Forbidden", { status: 403 });
}