Node.js 관리 SDK

@keenauth/node는 KeenAuth 관리 API용 TypeScript SDK입니다. Node.js 22 이상이 필요하며 런타임 의존성이 없습니다. 아직 npm에는 게시되지 않았습니다.

이 패키지에는 다음 진입점이 있습니다:

가져오기 경로 용도
@keenauth/node 관리 API 클라이언트, 관리 토큰, 페이지네이션 및 오류
@keenauth/node/webhooks API 클라이언트를 로드하지 않고 웹훅 서명 검증
@keenauth/node/bff 서버 앱의 로그인: Express 및 Fastify
@keenauth/node/middleware 베어러 토큰으로 API 보호
@keenauth/node/testing 오프라인 테스트 키트

클라이언트 생성

비밀 API 키 또는 제한된 API 키를 사용하는 경우:

import { KeenAuth } from "@keenauth/node";

const keenauth = new KeenAuth({
  baseUrl: "https://api.eu.keenauth.com/v1",
  apiKey: process.env.KEENAUTH_API_KEY!,
});

const user = await keenauth.users.getUser({ userId: "usr_01j9zzzzzzzzzzzzzzzzzzzz" });

baseUrl은 /v1이 포함된 리전 API 주소입니다. https를 사용해야 하며, http는 localhost에 대해서만 허용됩니다. 키는 Authorization 헤더에만 담기며 해당 주소로만 전송됩니다. 리디렉션은 따라가지 않습니다.

API 키 없이 인증

management 애플리케이션은 정적 키 대신 수명이 짧은 토큰으로 API를 호출할 수 있습니다. 애플리케이션은 개인 키로 클라이언트 어설션에 서명하고(private_key_jwt, RFC 7523), SDK는 이를 환경의 토큰 엔드포인트에서 교환합니다.

  1. 대시보드에서 management 유형의 애플리케이션과 필요한 권한을 가진 관리 클라이언트 레코드를 생성하십시오(관리 클라이언트 생성 참조).
  2. 키 쌍을 생성하고 공개 키를 애플리케이션의 public_key 자격 증명으로 등록하십시오. 예를 들어 ES256의 경우: openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out management-key.pem.
  3. 개인 키는 비밀 저장소에 보관하십시오. 그런 다음 SDK에 전달하십시오:
import { readFileSync } from "node:fs";
import { KeenAuth } from "@keenauth/node";

const keenauth = new KeenAuth({
  baseUrl: "https://api.eu.keenauth.com/v1",
  credentials: {
    issuer: "https://acme.eu.keenauth.app", // your environment's issuer, exactly
    clientId: "app_01j9zzzzzzzzzzzzzzzzzzzz",
    privateKey: readFileSync("management-key.pem", "utf8"), // PEM, a private JWK or a KeyObject
    scope: ["users:read", "users:write"], // optional; default: the application's default scopes
  },
});

SDK의 동작은 다음과 같습니다:

토큰 요청이 실패하면 OAuth code(예: invalid_client 또는 invalid_scope), description, status를 포함한 TokenError가 발생합니다. 공개 키를 개인 키 대신 사용하는 것과 같은 구성 오류는 클라이언트를 생성할 때 발생합니다.

apiKey와 credentials는 함께 사용할 수 없습니다.

재시도

SDK는 API가 429, 502, 503 또는 504를 반환하거나 연결이 실패하는 경우 요청을 총 최대 3회까지 재시도합니다. GET, PUT, DELETE 요청과 Idempotency-Key가 포함된 POST 요청만 재시도합니다. If-Match가 포함된 PATCH는 429에서만 재시도합니다. 속도 제한기가 요청을 실행하기 전에 거부하며, If-Match는 변경 사항이 두 번 적용되는 것을 방지하기 때문입니다. 그 밖의 PATCH는 재시도하지 않습니다.

SDK는 멱등성 키를 지원하는 모든 POST에 무작위 Idempotency-Key를 추가하므로, 재시도된 생성 요청이 객체를 두 번 만들지 않습니다. 직접 작성한 재시도도 안전하게 만들려면 같은 키를 직접 전달하십시오:

await keenauth.users.createUser({
  userCreateRequest: { email: "ada@example.com" },
  idempotencyKey: "signup-7d1f0c2a-0d7e-4c55",
});

페이지네이션

목록 엔드포인트는 한 페이지를 반환합니다. before와 after 커서가 포함된 data와 listMetadata가 제공됩니다. paginate는 커서를 자동으로 따라갑니다:

import { paginate } from "@keenauth/node";

for await (const user of paginate((p) => keenauth.users.listUsers(p), { limit: 100 })) {
  console.log(user.id);
  if (user.email === "ada@example.com") break; // stops without loading the next page
}

오류

오류 응답은 status, code, title, detail, requestId와 필드별 errors를 포함한 ProblemError(RFC 9457)가 됩니다. 메시지가 아니라 code를 확인하십시오. SDK가 알지 못하는 코드는 문자열로 계속 사용할 수 있습니다.

import { ProblemError } from "@keenauth/node";

try {
  await keenauth.users.getUser({ userId });
} catch (e) {
  if (e instanceof ProblemError && e.code === "not_found") {
    // …
  }
}

프록시의 HTML 페이지처럼 문제 문서가 없는 응답은 원본 response를 포함한 생성된 ResponseError가 됩니다.

웹훅 검증

KeenAuth는 Standard Webhooks v1 방식으로 웹훅에 서명합니다. 파싱하기 전에 원본 요청 본문으로 서명을 검증하십시오:

import express from "express";
import { Webhook, WebhookVerificationError } from "@keenauth/node/webhooks";

const webhook = new Webhook(process.env.KEENAUTH_WEBHOOK_SECRET!); // idp_whsec_…

const app = express();
app.post("/webhooks/keenauth", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = webhook.verify(req.body, req.headers);
  } catch (e) {
    if (e instanceof WebhookVerificationError) return res.status(400).send(e.kind);
    throw e;
  }
  // handle event.type, then answer quickly
  res.sendStatus(204);
});

웹훅은 두 번 이상 도착할 수 있습니다. 중복을 무시하려면 webhook-id를 사용하십시오.

타입이 지정된 이벤트와 클레임

@keenauth/node/events에는 공개 이벤트 스키마에서 생성된 모든 고객 이벤트의 타입이 있습니다. parseEvent는 검증된 본문을 확인하고 KeenAuthEvent | UnknownEvent를 반환합니다. isKnownEvent 이후에는 switch (event.type)이 이벤트를 하나의 타입으로 좁힙니다. @keenauth/node/webhooks의 verifyEvent는 두 단계를 모두 수행합니다:

import { verifyEvent } from "@keenauth/node/webhooks";
import { isKnownEvent } from "@keenauth/node/events";

const event = verifyEvent(webhook, req.body, req.headers); // WebhookVerificationError or EventParseError
if (!isKnownEvent(event)) {
  // A type this SDK version does not know yet. event.type and event.data.object.id are still typed.
  return res.sendStatus(204);
}
switch (event.type) {
  case "user.created":
    console.log(event.data.object.id);
    break;
  case "email_sending.suspended":
    console.log(event.data.object.reason); // "bounce_rate" | "complaint_rate" | (string & {})
    break;
}

@keenauth/node/claims는 검증된 토큰의 클레임 타입을 지정합니다: OpenID Connect Core의 parseIdTokenClaims와 RFC 9068의 parseAccessTokenClaims입니다. 이 함수들은 클레임 타입만 확인하고 아무것도 검증하지 않으므로, 검증기가 반환한 claims에 호출하십시오:

import { parseAccessTokenClaims } from "@keenauth/node/claims";

const at = await verifier.verifyAccessToken(token);
const claims = parseAccessTokenClaims(at.claims);
claims.client_id; // string
claims.org_roles; // string[] | undefined
claims["tenant_tier"]; // unknown: your own claims

맞지 않는 클레임은 이름과 함께 ClaimsParseError를 발생시킵니다.

지원 중단 경고

응답에 Deprecation(RFC 9745) 또는 Sunset(RFC 8594) 헤더가 있으면 SDK는 프로세스가 실행되는 동안 엔드포인트와 헤더 값마다 경고를 한 번씩 기록합니다. 경고에는 메서드, ID를 {id}로 바꾼 경로, 헤더 값이 포함됩니다. 토큰, 쿼리 문자열, 사용자 데이터는 포함되지 않습니다. 경고 때문에 오류가 발생하지 않습니다.

기본적으로 경고는 KeenAuthDeprecationWarning 타입과 함께 process.emitWarning으로 전달됩니다. 대신 로거로 보내려면 다음과 같이 하십시오:

const keenauth = new KeenAuth({
  baseUrl: "https://api.eu.keenauth.com/v1",
  apiKey: process.env.KEENAUTH_API_KEY!,
  onWarning: (message) => logger.warn(message),
});

텔레메트리 헤더

모든 요청에는 Idp-Client: keenauth-ts/<version> (node/<version>) 헤더가 포함됩니다. 이 헤더에는 개인 데이터가 없으며 끌 수 없습니다. KeenAuth는 이를 지원 목적과 지원 중단된 SDK 버전 안내에 사용합니다.