자체 UI에 CP 인증 붙이기 — @cp-platform/auth-sdk

외부 product (your product 처럼 별도 도메인에서 운영되는 앱) 가 자체 로그인 화면을 유지하면서 CP Platform 사용자 풀을 그대로 쓰는 표준 방식입니다.

SDK 한 줄 install로 끝:

npm i git+https://github.com/Copark-Dev/cp-auth-sdk.git#v0.5.2

왜 SDK인가: 이전에는 각 product가 PKCE / code-verifier / popup callback / in-app browser 차단 같은 걸 직접 짰습니다. 이제 SDK가 다 wrap. Threads / Toss / KakaoBank / 쿠팡 / 배민 / 당근 / Discord 등 30+ 인앱 브라우저 가드가 자동으로 적용됩니다.


1. Frontend — 로그인 / 가드 / OAuth

App root에 Provider 한 번:

import { CpAuthProvider } from "@cp-platform/auth-sdk/react";
 
<CpAuthProvider strict>
  <App />
</CpAuthProvider>

모든 OAuth 버튼 클릭 핸들러 한 줄:

import { useCpAuth } from "@cp-platform/auth-sdk/react";
 
function LoginButton() {
  const { guardOAuth } = useCpAuth();
  return (
    <button onClick={() => {
      if (guardOAuth("google") !== "proceed") return; // 인앱 자동 처리
      // ── 여기서부터 product 자체 OAuth 흐름 ──
      window.location.href = `${CP_AUTH_URL}/oauth/authorize?client_id=...&...`;
    }}>
      Google로 계속하기
    </button>
  );
}

guardOAuth("google") 반환:

  • "proceed" — 정상. 자체 흐름 진행.
  • "redirected" — Android 인앱이라 SDK가 자동으로 Chrome으로 escape. 페이지는 이미 unload 중 → caller는 즉시 return.
  • "blocked" — iOS 인앱이라 SDK가 <InAppBrowserModal/> 마운트. 사용자가 직접 Safari에서 다시 열어야 (Apple OS 정책).

Kakao 는 자동으로 "proceed" 반환합니다 (in-app webview 에서도 작동).

이메일/비번 직접 호출

import { CpAuthClient } from "@cp-platform/auth-sdk";
 
const auth = new CpAuthClient({
  authUrl: "https://auth.cp-platform.com",
  productOrigin: "https://your-app.com",
});
 
await auth.login(email, password);
await auth.signup({ email, password, phone, phone_consent: true });
await auth.requestPasswordReset(email);
await auth.confirmPasswordReset(tokenFromEmailLink, newPassword);
await auth.verifyEmail(tokenFromEmailLink);
await auth.logout();
 
if (auth.isLoggedIn()) {
  const user = await auth.getMe();
}

토큰은 SDK가 자동으로 localStorage 에 저장하고 만료 1분 전에 refresh. SSR / React Native 환경은 storage 옵션으로 커스텀 어댑터 주입 가능합니다 (SDK README 참고).


2. Backend — JWT 검증 + OAuth code exchange

Node / Express / Next API routes / Fastify 에서:

import { verifyCpJwt } from "@cp-platform/auth-sdk/jwt";
import { CpServerClient } from "@cp-platform/auth-sdk/server";
 
// 매 요청 인증 (middleware)
const { cpUserId, email } = await verifyCpJwt(bearerToken);
const { rows } = await db.query(
  "SELECT id FROM profiles WHERE cp_user_id = $1",
  [cpUserId]
);
 
// OAuth callback 처리
const cp = new CpServerClient(); // authUrl default = https://auth.cp-platform.com
const tokens = await cp.exchangeOAuthCode(code, codeVerifier);
const user = await cp.getUserInfo(tokens.access_token);
// getOrCreateLocalUser(user.sub) — 아래 Federated identity 패턴 참고

verifyCpJwt 는 Supabase JWKS 로 ES256 검증. 자체 JWT secret 관리 안 해도 됩니다.


3. Federated identity — 사용자 풀 통합

product 자체 DB는 그대로 두고, CP user_id 만 매핑 컬럼으로 잇습니다:

ALTER TABLE public.profiles
  ADD COLUMN cp_user_id UUID UNIQUE;

backend 흐름:

  1. 요청 옴 → verifyCpJwt(bearerToken) → cpUserId
  2. SELECT id FROM profiles WHERE cp_user_id = $1 로 자체 user_id 조회
  3. 없으면 자동 생성 (INSERT ... (cp_user_id, email, ...))
  4. 이후 비즈니스 로직은 자체 user_id 사용

이러면 CP 가입자가 product 들어왔을 때 첫 로그인 만에 자동으로 product 안에 row 가 생깁니다. 비밀번호 / OAuth 토큰은 CP 쪽 한 곳에만 존재.


4. In-app browser 가드 (자동)

<CpAuthProvider strict> 가 마운트되어 있으면:

환경동작
Android 인앱 (Threads / KakaoTalk / Toss / 쿠팡 / 배민 / Discord / …) + Google·Appleintent:// URL 로 Chrome 강제 호출. 사용자 클릭 0
iOS 인앱 + Google·Apple<InAppBrowserModal/> 자동 마운트. 사용자가 우상단 메뉴에서 Safari 로 직접 열도록 안내 (Apple OS 정책상 강제 호출 불가)
Kakao OAuth항상 통과 (Kakao 는 인앱에서도 작동)
일반 모바일 / 데스크톱 브라우저영향 없음

SDK 가 cover 하는 인앱:

  • 글로벌: Threads(Barcelona codename) / Instagram / Facebook / WeChat / X / TikTok / Snapchat / Pinterest / Reddit / Discord / Telegram / Slack / LINE
  • 한국: KakaoTalk / KakaoBank / KakaoPay / KakaoStory / Toss / Coupang / Baemin / Karrot / 11st / SSG / Emart / Lotte / Gmarket / Naver(검색·메일·카페) / Daum / BAND / EveryTime / Blind / KB / Woori / Shinhan / Hana / NH / IBK
  • Long-tail: strict: true 일 때 화이트리스트(Safari / Chrome / Samsung / Firefox / Edge / Opera / Brave / DuckDuckGo) 외 모바일 UA 는 모두 인앱으로 간주.

5. 자체 구현에서 마이그레이션

기존에 PKCE / popup / code-verifier 를 직접 짰다면:

  1. SDK 설치 + <CpAuthProvider> mount
  2. OAuth 버튼 핸들러 첫 줄에 guardOAuth("google") 가드 삽입 — 기존 PKCE / popup 흐름은 그대로 두어도 됩니다 (가드는 순수 추가)
  3. (선택) 자체 PKCE / fetch 코드를 CpAuthClient 호출로 점진 교체
  4. backend 의 자체 jose.jwtVerify 또는 httpx 래퍼를 verifyCpJwt / CpServerClient 로 교체
  5. .env 의 CP_JWT_* 같은 변수는 SDK default 가 production 값을 갖고 있어 대부분 삭제 가능 (staging / local 만 override)

여러 product 를 이 방식으로 통합 인증에 연결할 수 있습니다. 마이그레이션 PR 은 팀 내부 저장소(<your-org>)에서 참고 사례로 관리하세요.


6. SDK 버전

v0.1.0 — 현재 stable. 모든 기능 포함:

  • CpAuthClient + verifyCpJwt + CpServerClient
  • <CpAuthProvider> + useCpAuth().guardOAuth()
  • 30+ 인앱 브라우저 패턴 + strict 모드 + Android Chrome intent 자동 escape

외부 product 는 항상 v0.1.0 으로 pin 하면 됩니다.