Redis
 Computer >> コンピューター >  >> プログラミング >> Redis

Upstash RedisとCloudflare Workersで安全なAPIキーを作成する方法:ステップバイステップ完全ガイド

APIキーは、サービスへの「玄関の鍵」のようなものです。ユーザーを適切に受け入れながら、不正なアクセスからシステムを守ります。本記事では、高速なサーバーレスデータストアであるUpstash Redisと、エッジでリクエストを処理できるCloudflare Workersを組み合わせて、シンプルかつ安全なAPIキージェネレーターを構築する方法を解説します。新規サービスの立ち上げでも、既存アプリへの機能追加でも、APIキーの生成・保存・検証の流れを学び、効率的かつ安全な運用を実現しましょう。

APIキーとは?

APIキーとは、あなたのAPIへアクセスしようとするユーザーやアプリケーションを識別・認証するための一意のコードです。いわば「個人パス」のようなもので、サービスを利用したい人は、この鍵を提示することでアクセス権を持っていることを証明します。APIキーを活用すれば、リソースへのアクセス制御が可能になり、利用状況の追跡、レート制限の実施、不正アクセスの防止などにも役立ちます。APIアクセス管理とデータ保護のための、シンプルで効果的な手段といえるでしょう。

構築するもの

本ガイドでは、以下の2つのコア機能を備えたAPIキージェネレーターを作成します。

  1. カスタム設定付きで新しいAPIキーを生成する
  2. APIキーを検証しながらメタデータを取得する

主な機能は次のとおりです。

  • カスタマイズ可能なキープレフィックス
  • 有効期限の設定
  • レート制限
  • メタデータの保存
  • 所有者の識別

APIキーシステムの全体像

下記の図は、クライアント、Cloudflare Worker、Upstash Redisの間で行われる、APIキーの作成と検証のやり取りを示したものです。全体像を把握したところで、さっそく構築を始めましょう。

Upstash RedisとCloudflare Workersで安全なAPIキーを作成する方法:ステップバイステップ完全ガイド

前提条件

このチュートリアルを進めるには、以下が必要です。

  • Cloudflare Workersのアカウント
  • Upstashのアカウント
  • ローカルマシンにインストールされたNode.js

プロジェクト構成

今回のプロジェクトは、以下のディレクトリ構成になります。

folder-name/
├── src/
│ ├── config/
│ │ ├── generateApiKey.ts
│ │ └── schema-validation.ts
│ ├── lib/
│ │ └── ratelimit.ts
│ ├── routes/
│ │ ├── create.ts
│ │ └── verify.ts
│ ├── types/
│ │ └── api.ts
│ └── index.ts
├── package.json
└── wrangler.toml

ステップ1:プロジェクトのセットアップ

まずはプロジェクトを初期化し、必要な依存パッケージをインストールします。

プロジェクトディレクトリの作成

ターミナルを開き、以下のコマンドを実行してください。

mkdir keyflow
cd keyflow
npm init -y

依存パッケージのインストール

プロジェクトにはいくつかのパッケージが必要です。

npm install hono @upstash/redis @upstash/ratelimit @hono/zod-validator zod wrangler

各パッケージの役割は以下のとおりです。

  • @upstash/redis:サーバーレス環境向けのUpstash Redisクライアント
  • @upstash/ratelimit:Upstash Redis用のレート制限ライブラリ
  • @hono/zod-validator:Hono向けのリクエストバリデーションミドルウェア
  • zod:TypeScriptファーストのスキーマバリデーションライブラリ
  • wrangler:Cloudflare Workersの開発・デプロイ用CLIツール

Upstash Redisのセットアップ

  1. Upstashアカウントにログインし、新しいRedisデータベースを作成します。

Upstash RedisとCloudflare Workersで安全なAPIキーを作成する方法:ステップバイステップ完全ガイド

  1. 作成後、「REST API」セクションへ移動します。

Upstash RedisとCloudflare Workersで安全なAPIキーを作成する方法:ステップバイステップ完全ガイド

  1. .envセクションにあるUPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKENをコピーします。

Cloudflare Workersの設定

プロジェクトルートにwrangler.tomlファイルを作成し、以下の内容を記述します。

name = "keyflow"
main = "src/index.ts"
compatibility_date = "2023-05-18"
 
[vars]
UPSTASH_REDIS_REST_URL = "your-redis-url"
UPSTASH_REDIS_REST_TOKEN = "your-redis-token"

"your-redis-url""your-redis-token"は、Upstashからコピーした値に置き換えてください。

ステップ2:API型の定義

まず、APIリクエストとレスポンスのためのTypeScriptインターフェースを定義します。これにより、アプリケーション全体で型安全性を維持できます。src/types/api.tsというファイルを新規作成しましょう。

export type CreateKeyRequest = {
 apiId: string;
 prefix?: string;
 byteLength?: number;
 ownerId?: string;
 name: string;
 meta?: Record<string, unknown>;
 expires?: number;
 ratelimit?: {
 type: "fast" | "consistent";
 limit: number;
 refillRate: number;
 refillInterval: number;
 };
};
 
export type CreateKeyResponse = {
 key: string;
 keyId: string;
};
 
export type VerifyKeyRequest = {
 key: string;
};
 
export type VerifyKeyResponse = {
 valid: boolean;
 ownerId?: string;
 meta?: Record<string, unknown>;
 expires?: number;
 ratelimit?: {
 limit: number;
 remaining: number;
 reset: number;
 };
};
 
export type Env = {
 UPSTASH_REDIS_REST_URL: string;
 UPSTASH_REDIS_REST_TOKEN: string;
};
 

ステップ3:APIキー生成の実装

次に、APIキーを生成するユーティリティ関数を作成します。src/config/generateApiKey.tsというファイルを新規作成してください。

export function generateApiKey(
 prefix: string | undefined,
 byteLength: number,
): string {
 const randomBytes = crypto.getRandomValues(new Uint8Array(byteLength));
 const key = btoa(String.fromCharCode(...new Uint8Array(randomBytes)))
 .replace(/\+/g, "-")
 .replace(/\//g, "_")
 .replace(/=/g, "");
 return prefix ? `${prefix}_${key}` : key;
}
 

この関数は、暗号学的に安全なランダムバイトを使用してAPIキーを生成し、Base64でエンコードした後、URLセーフな形式に変換します。

ステップ4:レート制限の実装

src/lib/ratelimit.tsファイルを作成します。

import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis/cloudflare";
import type { Context, Next } from "hono";
import { env } from "hono/adapter";
import type { Env } from "../types/api";
 
// レート制限ミドルウェア
export async function rateLimitMiddleware(c: Context, next: Next) {
 const { UPSTASH_REDIS_REST_TOKEN, UPSTASH_REDIS_REST_URL } = env<Env>(c);
 
 const redis = new Redis({
 url: UPSTASH_REDIS_REST_URL,
 token: UPSTASH_REDIS_REST_TOKEN,
 });
 
 const ratelimit = new Ratelimit({
 redis: redis,
 limiter: Ratelimit.slidingWindow(5, "30 s"),
 });
 
 const ip = c.req.header("CF-Connecting-IP") || "127.0.0.1";
 const { success, limit, remaining, reset } = await ratelimit.limit(ip);
 
 if (!success) {
 return c.json({ error: "Rate limit exceeded" }, 429);
 }
 
 c.header("X-RateLimit-Limit", limit.toString());
 c.header("X-RateLimit-Remaining", remaining.toString());
 c.header("X-RateLimit-Reset", reset.toString());
 
 await next();
}

このミドルウェアは、30秒間に5リクエストまでというスライディングウィンドウ方式のレート制限をIPアドレス単位で適用します。制限を超えた場合は429エラーを返し、通常時はレート制限の状態をレスポンスヘッダーに付与します。

ステップ5:APIルートの作成

ここからは、メインのアプリケーションファイルを整え、APIルートを作成していきます。Honoアプリケーションには、新しいAPIキーを生成する/keys/createと、既存キーを検証する/keys/verifyという2つの主要ルートを、それぞれ別ファイルとして実装します。

1. APIキー作成ルートの実装

src/routes/create.tsファイルを作成します。

import { zValidator } from "@hono/zod-validator"
import { Redis } from "@upstash/redis/cloudflare"
import { Hono } from "hono"
import { generateApiKey } from "../config/generateApiKey"
import { createApiKeySchema } from "../config/schema-validation"
import type { CreateKeyRequest, CreateKeyResponse, Env } from "../types/api"
 
const create = new Hono<{
 Bindings: Env
}>()
 
create.post(
 "/create",
 zValidator("json", createApiKeySchema, (result, c) => {
 if (!result.success) {
 return c.text("Invalid!", 400)
 }
 }),
 async (c) => {
 // Redisクライアントの初期化
 const { UPSTASH_REDIS_REST_TOKEN, UPSTASH_REDIS_REST_URL } = c.env
 const redis = new Redis({
 url: UPSTASH_REDIS_REST_URL,
 token: UPSTASH_REDIS_REST_TOKEN,
 })
 
 const body = await c.req.json<CreateKeyRequest>()
 
 // 一意の識別子とAPIキーを生成
 const keyId = crypto.randomUUID()
 const key = generateApiKey(body.prefix, body.byteLength || 16)
 
 const keyData = {
 ...body,
 key,
 keyId,
 createdAt: Date.now(),
 }
 
 const encodedKey = encodeURIComponent(key)
 
 try {
 // キーデータと参照用の情報をRedisに保存
 await redis.set(`key:${keyId}`, JSON.stringify(keyData))
 await redis.set(`lookup:${encodedKey}`, keyId)
 
 return c.json<CreateKeyResponse>({ key, keyId })
 } catch (error) {
 console.error("Error in /keys/create:", error)
 return c.json({ error: "Internal Server Error" }, 500)
 }
 }
)
 
export default create

2. APIキー検証ルートの実装

src/routes/verify.tsファイルを作成します。

import { zValidator } from "@hono/zod-validator";
import { Redis } from "@upstash/redis/cloudflare";
import { Hono } from "hono";
import { verifyApiKeySchema } from "../config/schema-validation";
import type {
 CreateKeyRequest,
 Env,
 VerifyKeyRequest,
 VerifyKeyResponse,
} from "../types/api";
 
// 環境変数のバインディング付きでHonoアプリを初期化
const verify = new Hono<{ Bindings: Env }>();
 
// APIキー検証用のPOSTルートを定義
verify.post(
 "/verify",
 // リクエストボディをスキーマで検証
 zValidator("json", verifyApiKeySchema, (result, c) => {
 if (!result.success) {
 return c.text("Invalid!", 400); // 検証失敗時は400を返す
 }
 }),
 async (c) => {
 // 環境変数でRedisをセットアップ
 const { UPSTASH_REDIS_REST_TOKEN, UPSTASH_REDIS_REST_URL } = c.env;
 const redis = new Redis({
 url: UPSTASH_REDIS_REST_URL,
 token: UPSTASH_REDIS_REST_TOKEN,
 });
 
 const body = await c.req.json<VerifyKeyRequest>();
 if (!body.key) {
 return c.json({ error: "key is required" }, 400); // キーの指定は必須
 }
 
 const encodedKey = encodeURIComponent(body.key);
 const keyId = await redis.get<string>(`lookup:${encodedKey}`); // エンコード済みキーからキーIDを取得
 
 if (!keyId) {
 return c.json<VerifyKeyResponse>({ valid: false }); // キーが見つからない
 }
 
 const keyDataString = await redis.get<string>(`key:${keyId}`); // キーIDからキーデータを取得
 
 if (!keyDataString || typeof keyDataString !== "string") {
 return c.json<VerifyKeyResponse>({ valid: false }); // キーデータが欠落または無効
 }
 
 let keyData: CreateKeyRequest & {
 key: string;
 keyId: string;
 createdAt: number;
 };
 
 try {
 keyData = JSON.parse(keyDataString); // キーデータをパース
 } catch (parseError) {
 // パースエラー時は無効なデータを削除して対応
 console.error("Key data parse error:", parseError);
 await Promise.all([
 redis.del(`key:${keyId}`),
 redis.del(`lookup:${encodedKey}`),
 ]);
 return c.json(
 {
 error: "Invalid key data in storage",
 details: parseError instanceof Error ? parseError.message : "Unknown parse error",
 valid: false,
 },
 500,
 );
 }
 
 // 有効期限のチェック
 if (keyData.expires && keyData.expires < Date.now()) {
 await Promise.all([
 redis.del(`key:${keyId}`),
 redis.del(`lookup:${encodedKey}`),
 ]);
 return c.json<VerifyKeyResponse>({ valid: false });
 }
 
 // 検証結果とメタデータを含むレスポンスを組み立て
 const response: VerifyKeyResponse = {
 valid: true,
 ownerId: keyData.ownerId,
 meta: keyData.meta,
 expires: keyData.expires,
 };
 
 if (keyData.ratelimit) {
 response.ratelimit = {
 limit: keyData.ratelimit.limit,
 remaining: keyData.ratelimit.limit,
 reset: Date.now() + keyData.ratelimit.refillInterval,
 };
 }
 
 return c.json(response); // 検証レスポンスを返す
 },
);
 
export default verify;
 

3. メインファイルindex.tsの実装

最後に、メインファイルsrc/index.tsを実装し、create.tsverify.ts、そしてrateLimitMiddleWareを読み込みます。

import { Hono } from "hono";
import { rateLimitMiddleware } from "./lib/ratelimit";
import create from "./routes/create";
import verify from "./routes/verify";
import type { Env } from "./types/api";
 
const app = new Hono<{
 Bindings: Env;
}>().basePath("/keys");
 
app.use("*", rateLimitMiddleware);
 
// 作成ルートと検証ルートを登録
app.route("/", create);
app.route("/", verify);
 
export default app;
 

ステップ6:デプロイ

アプリケーションの準備ができたら、KeyflowをCloudflare Workersへデプロイしましょう。

  1. Wrangler CLIをグローバルにインストールします。

    npm install -g wrangler
    
  2. Cloudflareアカウントで認証します。

    wrangler login
    
  3. Workerをデプロイします。

    wrangler deploy
    

ステップ7:APIのテスト

デプロイが完了したら、実際にAPIを動かしてみましょう。

新しいAPIキーの作成

curl -X POST https://keyflow.<your-subdomain>.workers.dev/keys/create \
 -H "Content-Type: application/json" \
 -d '{
 "apiId": "my-api",
 "prefix": "prod",
 "name": "Production API Key",
 "expires": 1735689600000,
 "meta": {
 "environment": "production",
 "team": "backend"
 }
 }'

APIキーの検証

curl -X POST https://keyflow.<your-subdomain>.workers.dev/keys/verify \
 -H "Content-Type: application/json" \
 -d '{
 "key": "prod_AbC123XyZ..."
 }'

<your-subdomain>は自分のCloudflare Workersサブドメインに、prod_AbC123XyZ...はcreateエンドポイントで実際に生成されたキーに置き換えてください。

まとめ

APIキージェネレーターを構築することは、アプリケーションのAPIを保護し、サービスへのアクセス権を管理するうえで重要な第一歩です。キーの生成・検証・管理を一元的に行えるシステムを導入すれば、強固なアクセス制御レイヤーが加わり、データの安全性と整理された運用が実現します。

堅牢なAPIキージェネレーターに必要な要素を振り返ってみましょう。

  1. 安全なキー生成:カスタムプレフィックスや固定長などのオプションを備えた一意のキーは、推測されにくく、各キーの識別性も高まります。
  2. 検証と有効期限:検証チェックと有効期限を組み合わせることで、キーが一定期間のみ有効になり、必要に応じてアクセスを柔軟に制御できます。
  3. メタデータとレート制限:キーごとに追加情報を保存し、レート制限を設けることで、利用状況の監視やアクティビティの追跡が可能になり、APIの乱用を防げます。

Upstash RedisとCloudflare Workersのようなツールを活用すれば、サーバーレスかつグローバルに分散したキー管理システムを容易に構築でき、優れたスケーラビリティと高い効率性を両立できます。

この基盤があれば、APIへのアクセスを安全かつ管理しやすい状態に保てます。リソースがしっかり保護され、監視もしやすい環境によって、安心してサービスを運用できるでしょう。

  1. Redis分散ロック徹底解説:実証済みパターン、よくある落とし穴、実践的な活用法

    はじめに分散ロックは、本番環境で実際に依存するようになるまで、非常にシンプルなものに聞こえます。あるプロセスがリソースへの排他的アクセスを必要としている。複数のサーバーが稼働している。Redisがその中間に位置する。「Redisにロックを置けばあとは進むだけ」という発想は、一見まっすぐで理にかなっているように感じられます。しばらくの間、このアプローチはうまく機能しているように見えます。しかし、ある日プロセスがクラッシュしたり、ネットワーク遅延が発生したり、レイテンシが急上昇したりします。突然、2つのプロセスが同じロックを取得してしまったり、どのプロセスもロックを所有していない状態になったり、ロ

  2. Redis HGETALLコマンドの使い方 – ハッシュに含まれるすべてのフィールドと値のペアを一括取得する方法

    このチュートリアルでは、Redisのキーに保存されたハッシュ値に含まれるすべてのフィールドと値のペアを取得する方法について解説します。この操作には、RedisのHGETALLコマンドを使用します。 HGETALLコマンドとは HGETALLコマンドは、指定したキーに保存されているハッシュ値に含まれるすべてのフィールドと、それぞれに関連付けられた値を返すコマンドです。 動作のポイントは以下の通りです。 キーが存在しない場合は、空のリストが返されます(エラーにはなりません)。 キーは存在するが、その値がハッシュ型ではない場合は、エラーが返されます。 構文 redis host:port>