브라우저 — @yetdapay/license-web
핵심 요약
브라우저 SDK는 라이선스를 활성화하고 기기 키로 보호 요청을 서명합니다. 최종 실행 허용 여부는 서버가 판단해야 합니다.
Worker 프리셋과 활성화 예제부터 따라 하세요. 기기 키를 초기화하면 기기 등록 슬롯이 추가로 사용될 수 있습니다.
sdk/license/web/ — 바이어 측 클라이언트. 추출 불가 WebCrypto P-256 디바이스 키페어를 보유하고, 라이선스를 활성화하고, entitlement를 저장하고, 모든 게이트된 요청에 새 DPoP 스타일 증명으로 서명합니다.
보장 (및 비보장)
| 보장됨 | 방법 |
|---|---|
| 디바이스 개인 키는 JS로 추출 불가 | generateKey({extractable:false}); CryptoKey 자체는 IndexedDB에 존재 |
| 요청 변조/리플레이는 서버 측에서 차단 | 요청당 증명 {htu,htm,iat,jti,h,jwk}; jti는 서버 측에서 원자적으로 소비 |
| 토큰 복사만으로는 무용 | 모든 호출이 jkt 디바이스 바인딩을 재증명 |
| 폐기는 즉시 적용 | 서버가 가드된 모든 호출에서 라이선스 상태를 재조회 |
보장되지 않음: 클라이언트 번들 난독화/무결성, DRM, 오프라인 보호, 완전히 적대적인 브라우저에 대한 어떤 것도. 제한 사항 참조.
API 표면 (sdk/license/web/src/index.js)
import {
createLicenseClient, createWorkerConfig, WORKER_ACTIVATE_PATH,
configure, getDefaultClient,
activate, signedFetch, isEntitled, getEntitlement, restore,
clearEntitlement, resetDevice,
// stores & crypto primitives
createIndexedDbKeyStore, createMemoryKeyStore, loadOrCreateDeviceKey,
createLocalStorageEntitlementStore, createMemoryEntitlementStore,
createDpopProof, LicenseError, LicenseErrorCode,
} from '@yetdapay/license-web';2
3
4
5
6
7
8
9
10
클라이언트 vs 편의 함수
createLicenseClient(options)는 클라이언트 객체를 반환하고, 최상위 configure()/activate()/signedFetch()/… 함수는 모듈 수준 기본 클라이언트에서 동작합니다 — 한 가지 스타일을 선택하고 섞지 마세요.
호스티드 Worker와 페어링
const client = createLicenseClient(
createWorkerConfig({
baseUrl: 'https://api.example.com',
productId: 'prod_abc123',
})
);
await client.activate({ licenseKey: 'NG-…-…' });
const res = await client.signedFetch('/api/v1/licenses/status');2
3
4
5
6
7
8
createWorkerConfig()는 activatePath를 Worker 라우트 /api/v1/licenses/activate로 설정합니다(WORKER_ACTIVATE_PATH로 export됨). SDK 기본값(/api/license/activate)은 자체 호스팅 @yetdapay/license-server를 타겟합니다 — 활성화 경로 표 참조.
signedFetch가 생성하는 요청 형태
Authorization: Entitlement <entitlement-jws>- 페이로드
{htu, htm, iat, jti, h, jwk}를 가진DPoP: <proof-jws>htu= 동일 originpathname + query(전체 URI가 아님)h=base64url(sha256(raw request body))
401에서 클라이언트는 entitlement를 한 번 갱신하고 새로운 증명으로 재시도합니다 — 제한된 재시도, 루프 없음.
저장소 어댑터
createIndexedDbKeyStore— 디바이스CryptoKey영속(브라우저).createLocalStorageEntitlementStore/createMemoryEntitlementStore— entitlement 영속;restore()가 저장된 entitlement를 다시 로드합니다.
resetDevice() 경고
resetDevice()는 로컬 디바이스 아이덴티티를 지웁니다 — 서버 측 license_bindings 슬롯을 해제하지 않으므로, max_devices 슬롯 하나를 영구적으로 소진합니다(기록된 갭; unbind는 NOT_IMPLEMENTED). 슬롯 손실을 받아들일 때만 호출하세요.
오류 처리
LicenseError는 백엔드 {code,message} body의 .code를 그대로와 .status(실제 HTTP 상태)를 가집니다. Worker/SDK 코드 델타는 계약 매트릭스 §2에 문서화되어 있습니다.