Node.js 管理 SDK
@keenauth/node は、KeenAuth 管理 API 用の TypeScript SDK です。Node.js 22 以降が必要で、ランタイム依存関係はありません。まだ npm には公開されていません。
このパッケージには次のエントリポイントがあります:
| Import | 用途 |
|---|---|
@keenauth/node |
管理 API クライアント、管理トークン、ページネーション、エラー |
@keenauth/node/webhooks |
API クライアントを読み込まずに Webhook の署名を検証する |
@keenauth/node/bff |
サーバーアプリのサインイン: Express および Fastify |
@keenauth/node/middleware |
バアラートークンによる API の保護 |
@keenauth/node/testing |
オフラインテストキット |
クライアントを作成する
シークレットキーまたは制限付き 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 は、リージョンの API アドレスに /v1 を付けたものです。https を使う必要があり、http は localhost の場合のみ許可されます。キーは Authorization ヘッダーでのみ送信され、そのアドレスにのみ送られます。リダイレクトには追従しません。
API キーを使わずに認証する
management アプリケーションは、静的なキーの代わりに短期間有効なトークンを使って API を呼び出せます。アプリケーションは秘密鍵でクライアントアサーションに署名し(private_key_jwt、RFC 7523)、SDK がそれを環境のトークンエンドポイントと交換します。
- ダッシュボードで、
managementタイプのアプリケーションと、必要な権限を持つ管理クライアントレコードを作成します(管理クライアントを作成する を参照)。 - 鍵ペアを生成し、公開鍵をアプリケーションの
public_key認証情報として登録します。例えば ES256 の場合:openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out management-key.pem。 - 秘密鍵はシークレットストアに保管してください。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 の動作は次のとおりです:
grant_type=client_credentialsを<issuer>/oauth2/tokenに送信し、resourceには管理 API の識別子を設定します。既定値はbaseUrlのオリジン(例:https://api.eu.keenauth.com)です。変更するにはcredentials.audienceを設定します。- リクエストごとに新しいクライアントアサーションを生成します。
issとsubはクライアント ID、audは issuer、jtiはランダムな値で、アサーションの有効期間は 120 秒です。kidヘッダーは鍵の RFC 7638 サムプリントで、アプリケーションに複数の鍵がある場合(鍵のローテーション時)に KeenAuth が鍵を特定するために使われます。 - アルゴリズムは鍵の種類に従います。RSA 鍵は
RS256で署名します(RSA-PSS の場合はalg: "PS256"を設定)。P-256 鍵はES256、Ed25519 鍵はEdDSAを使います。RSA 鍵は少なくとも 2048 ビット必要です。 - アクセストークンはキャッシュされ、有効期限の 60 秒前に更新されます。同時に行われた呼び出しは 1 回の更新を共有します。API が
401を返した場合、キャッシュされたトークンは破棄され、次の呼び出しで新しいトークンが取得されます。
トークンのリクエストが失敗すると、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 はリトライされません。
- レスポンスに
Retry-Afterヘッダー(秒数または HTTP 日付)がある場合、SDK はその時間待機します。60 秒を超えるRetry-Afterは待機せず、そのレスポンスを返します。 - それ以外の場合は、0.5 秒、次に 1 秒待機します。どちらも ±25% のランダムなジッターが加えられます。
timeoutMs(既定値は 30 秒)は、リトライを含む呼び出し全体を制限します。待機によってその時間を超える場合、SDK は代わりに最後のレスポンスを返します。
SDK は、対応する操作が受け付ける POST のすべてにランダムな Idempotency-Key を追加するため、リトライされた作成処理によってオブジェクトが 2 つ作られることはありません。独自のリトライも安全にするには、同じキーを自分で渡してください:
await keenauth.users.createUser({
userCreateRequest: { email: "ada@example.com" },
idempotencyKey: "signup-7d1f0c2a-0d7e-4c55",
});
ページネーション
一覧のエンドポイントは 1 ページを返します。data と、before および after カーソルを含む 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
}
beforeを指定しない場合は、カーソルがnullになるまでafterを使って前方に進みます。beforeを指定した場合は後方に進みます。limitはページサイズ(1〜100)で、すべてのリクエストで送信されます。その他のパラメーターも保持されます。paginatePagesは、項目ではなくページ単位で結果を返します。- サーバーが同じカーソルを 2 回返した場合、ループは
PaginationErrorで停止します。
エラー
エラーレスポンスは、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 になります。
Webhook を検証する
KeenAuth は Standard Webhooks v1 方式で Webhook に署名します。解析する前に、生のリクエストボディを使って署名を検証してください:
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);
});
- SDK は
webhook-id、webhook-timestamp、webhook-signatureヘッダーを確認します。タイムスタンプは、サーバーのクロックから前後いずれの方向にも 5 分以内である必要があります。 - シークレットをローテーションする間は、両方のシークレットを渡します:
new Webhook([newSecret, oldSecret])。いずれかのv1署名がいずれかのシークレットと一致すれば、リクエストは受け入れられます。 - 署名は定数時間で比較されます。
e.kindで失敗の内容がわかります:headers(ヘッダーがない)、timestamp(数値ではない、または許容範囲外)、signature(一致する署名がない)、secret(設定されたシークレットが有効なidp_whsec_値ではない。new Webhookが発生させる設定エラー)。各 kind にはWebhookSignatureErrorなどの専用クラスもあります。verifyは解析済みの JSON ボディを返します。verifyRawは検証済みのバイト列を返します。
Webhook は複数回届く場合があります。重複を無視するには webhook-id を使用してください。
型付きイベントとクレーム
@keenauth/node/events には、公開イベントスキーマから生成された、顧客向けイベントごとの型があります。parseEvent は検証済みのボディを確認し、KeenAuthEvent | UnknownEvent を返します。isKnownEvent の後で switch (event.type) を使うと、イベントが 1 つの型に絞り込まれます。@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;
}
parseEventは JSON テキスト、バイト列、またはデコード済みの値を受け付けます。エンベロープと、その型のスキーマが宣言するメンバーを確認します。メンバーが欠けている、または型が違う場合は、data.object.reasonのようなパスを付けてEventParseErrorがスローされます。- 未知の型、または、この SDK がモデル化していない
schema_versionを持つ既知の型はUnknownEventになります。これはエラーではありません。 - スキーマで宣言されていないメンバーは、オブジェクトに残ります。
- 列挙型のフィールドは、既知の値に
stringを加えた型になるため、サーバーからの新しい値も型チェックとパースを通ります。
@keenauth/node/claims は、検証済みトークンのクレームに型を付けます。parseIdTokenClaims(OpenID Connect Core)と parseAccessTokenClaims(RFC 9068)です。これらはクレームの型を確認しますが、検証は行いません。verifier が返す 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 はプロセスの存続期間中、エンドポイントとヘッダー値ごとに 1 回警告を出力します。警告にはメソッド、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 バージョンについてお知らせするためにも使います。