メインコンテンツへスキップ

AI を使って Auth0 を統合する

Claude Code、Cursor、GitHub Copilot などの AI コーディングアシスタントを使用している場合は、agent skills を使って、数分で Auth0 API 認証を自動的に追加できます。インストール:
npx skills add auth0/agent-skills --skill auth0-quickstart --skill auth0-fastify-api
次に、AI アシスタントに以下のように依頼します。
Add Auth0 JWT authentication to my Fastify API
AI アシスタントが、Auth0 API の作成、認証情報の取得、@auth0/auth0-fastify-api のインストール、プラグインの設定、JWT の検証による API エンドポイントの保護を自動的に行います。agent skills の完全なドキュメント →
前提条件: 開始する前に、以下がインストールされていることを確認してください。
  • Node.js 20 LTS 以降
  • npm 10 以降、または yarn 1.22 以降、または pnpm 8 以降
インストールの確認: node --version && npm --versionFastify のバージョン互換性: このクイックスタートは Fastify 5.x 以降で動作します。

はじめに

このクイックスタートでは、JWT アクセストークンを使用して Fastify API のエンドポイントを保護する方法を説明します。Auth0 のアクセストークンを検証し、保護されたリソースへのアクセスを許可する安全な API を構築します。
1

新しいプロジェクトを作成

Fastify API 用の新しいディレクトリを作成し、Node.js プロジェクトを初期化します。
mkdir auth0-fastify-api && cd auth0-fastify-api
プロジェクトを初期化
npm init -y
プロジェクト構造を作成する
touch server.js .env
2

Auth0 Fastify API SDK をインストールする

必要な依存関係をインストールする
npm install @auth0/auth0-fastify-api fastify dotenv
package.json を更新し、起動スクリプトを追加します。
package.json
{
  "name": "auth0-fastify-api",
  "version": "1.0.0",
  "type": "module",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "node --watch server.js"
  },
  "dependencies": {
    "@auth0/auth0-fastify-api": "^1.2.0",
    "dotenv": "^16.3.1",
    "fastify": "^5.0.0"
  }
}
3

Auth0 API を設定する

次に、Auth0 テナントに新しい API を作成し、環境変数をプロジェクトに追加する必要があります。Auth0 API を設定する方法は 2 つあります。CLI コマンドを使用するか、Dashboard から手動で設定できます。
Auth0 API を作成するには、プロジェクトのルートディレクトリで次のコマンドを実行します。
# Auth0 CLI をインストール(まだインストールしていない場合)
brew tap auth0/auth0-cli && brew install auth0

# Auth0 API を作成
auth0 apis create \
  --name "My Fastify API" \
  --identifier https://my-fastify-api.example.com
作成後、IdentifierDomain の値をコピーし、.env ファイルを作成します。
.env
AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN
AUTH0_AUDIENCE=YOUR_API_IDENTIFIER
このコマンドでは、次の処理が行われます。
  1. 認証済みかどうかを確認し、必要に応じてログインを求める
  2. 指定した Identifier で Auth0 API を作成する
  3. ドメインと Identifier を含む API の詳細を表示する
.env ファイルが存在することを確認します: cat .env (Mac/Linux) または type .env (Windows)
4

Auth0 APIプラグインを設定する

Fastify サーバーを作成し、Auth0 API プラグインを登録します。
server.js
import 'dotenv/config';
import Fastify from 'fastify';
import fastifyAuth0Api from '@auth0/auth0-fastify-api';

const fastify = Fastify({ logger: true });
const port = process.env.PORT || 3001;

// Auth0 APIプラグインを登録する
await fastify.register(fastifyAuth0Api, {
  domain: process.env.AUTH0_DOMAIN,
  audience: process.env.AUTH0_AUDIENCE,
});

// サーバーを起動する
fastify.listen({ port }, (err) => {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
  fastify.log.info(`API server running at http://localhost:${port}`);
});
これで行われること:
  • Auth0 API プラグインを Auth0 のドメインと API のオーディエンスで登録します
  • 受信リクエストに対する JWT 検証を設定します
  • ルートを保護するための requireAuth() preHandler を利用できるようにします
5

API ルートを作成

server.js にパブリックルートと保護されたルートを追加します。
server.js
import 'dotenv/config';
import Fastify from 'fastify';
import fastifyAuth0Api from '@auth0/auth0-fastify-api';

const fastify = Fastify({ logger: true });
const port = process.env.PORT || 3001;

// Auth0 APIプラグインを登録
await fastify.register(fastifyAuth0Api, {
  domain: process.env.AUTH0_DOMAIN,
  audience: process.env.AUTH0_AUDIENCE,
});

// パブリックルート - 認証不要
fastify.get('/api/public', async (request, reply) => {
  return {
    message: 'Hello from a public endpoint! You don\'t need to be authenticated to see this.',
    timestamp: new Date().toISOString(),
  };
});

// 保護されたルート - 有効なアクセストークンが必要
fastify.get('/api/private', {
  preHandler: fastify.requireAuth()
}, async (request, reply) => {
  return {
    message: 'Hello from a protected endpoint! You successfully authenticated.',
    user: request.user.sub,
    timestamp: new Date().toISOString(),
  };
});

// 保護されたルート - トークンからユーザー情報を返す
fastify.get('/api/profile', {
  preHandler: fastify.requireAuth()
}, async (request, reply) => {
  return {
    message: 'Your user profile from the access token',
    profile: request.user,
  };
});

// サーバーを起動
fastify.listen({ port }, (err) => {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
  fastify.log.info(`API server running at http://localhost:${port}`);
});
要点:
  • パブリックルートでは認証は不要です
  • 保護されたルートでは、有効な JWT を必須にするため、preHandler: fastify.requireAuth() を使用します
  • request.user には、認証済みリクエストのデコードされた JWT クレームが格納されます
  • sub クレームには、ユーザーの一意の識別子が格納されます
6

API を起動する

開発サーバーを起動します:
npm run dev
API は http://localhost:3001 で実行中です。
Node.js 20 以降では、--watch フラグによりファイルの変更時にサーバーが自動的に再起動されます。
7

API をテストする

公開エンドポイントをテストします (認証は不要です) :
curl http://localhost:3001/api/public
次のように表示されます:
{
  "message": "Hello from a public endpoint! You don't need to be authenticated to see this.",
  "timestamp": "2024-01-15T10:30:00.000Z"
}
トークンなしで保護されたエンドポイントをテストします (失敗するはずです) :
curl http://localhost:3001/api/private
401 Unauthorized エラーが表示されます。
{
  "error": "Unauthorized",
  "message": "No authorization token was found"
}
有効なトークンでテストするには、次の手順を実行します。
  1. ユーザーを認証するクライアントアプリケーション (Web アプリまたはモバイルアプリ) を作成する
  2. API 用のアクセストークンを要求するようにクライアントを設定する (audience パラメーターを使用)
  3. そのアクセストークンを Authorization ヘッダーで使用する
トークンの例:
curl http://localhost:3001/api/private \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
チェックポイントこれで、保護されたAPIができているはずです。API は次のように動作します。
  1. 認証なしでパブリックエンドポイントへのリクエストを受け付ける
  2. 有効なトークンなしで保護されたエンドポイントへのリクエストを拒否する
  3. JWT トークンを Auth0 のドメインと対象者に対して検証する
  4. トークンのクレームから、request.user を通じてユーザー情報を提供する

高度な使い方

Token インターフェースを拡張して、アクセストークン内のカスタムクレームの型安全性を確保します。
server.ts
import '@auth0/auth0-fastify-api';

// カスタムクレームを含むように Token インターフェースを拡張
declare module '@auth0/auth0-fastify-api' {
  interface Token {
    sub: string;
    permissions?: string[];
    'https://myapp.com/roles'?: string[];
    email?: string;
    email_verified?: boolean;
  }
}
これで、TypeScript がカスタムクレームを認識するようになります。
server.ts
fastify.get('/api/profile', {
  preHandler: fastify.requireAuth()
}, async (request, reply) => {
  // TypeScript はこれらのプロパティを認識します
  const userRoles = request.user['https://myapp.com/roles']; // string[] | undefined
  const permissions = request.user.permissions; // string[] | undefined
  const email = request.user.email; // string | undefined

  return {
    userId: request.user.sub,
    roles: userRoles || [],
    permissions: permissions || [],
    email: email,
  };
});
カスタムクレームでは、標準のOIDCクレームでない限り、名前空間付き URL (例: https://myapp.com/roles) を使用する必要があります。カスタムクレームの詳細はこちら
アクセストークン内の特定の権限を確認します。
server.js
// 特定の権限を確認するミドルウェア
function requirePermission(permission) {
  return async (request, reply) => {
    const permissions = request.user.permissions || [];

    if (!permissions.includes(permission)) {
      return reply.status(403).send({
        error: 'Forbidden',
        message: `Missing required permission: ${permission}`
      });
    }
  };
}

// 'read:messages' 権限が必要なルート
fastify.get('/api/messages', {
  preHandler: [
    fastify.requireAuth(),
    requirePermission('read:messages')
  ]
}, async (request, reply) => {
  return {
    messages: ['Message 1', 'Message 2', 'Message 3']
  };
});

// 'write:messages' 権限が必要なルート
fastify.post('/api/messages', {
  preHandler: [
    fastify.requireAuth(),
    requirePermission('write:messages')
  ]
}, async (request, reply) => {
  return {
    message: 'Message created successfully',
    id: 'msg_123'
  };
});
権限は Auth0 API の設定で指定し、クライアントに付与する必要があります。API 権限の詳細はこちら
カスタムクレームを使用して、ロールベースのアクセス制御を実装します。
server.js
// 特定のロールを確認するミドルウェア
function requireRole(role) {
  return async (request, reply) => {
    const roles = request.user['https://myapp.com/roles'] || [];

    if (!roles.includes(role)) {
      return reply.status(403).send({
        error: 'Forbidden',
        message: `Missing required role: ${role}`
      });
    }
  };
}

// admin 専用ルート
fastify.get('/api/admin/users', {
  preHandler: [
    fastify.requireAuth(),
    requireRole('admin')
  ]
}, async (request, reply) => {
  return {
    users: [
      { id: 1, name: 'User 1' },
      { id: 2, name: 'User 2' }
    ]
  };
});

// manager または admin 用ルート
function requireAnyRole(...roles) {
  return async (request, reply) => {
    const userRoles = request.user['https://myapp.com/roles'] || [];
    const hasRole = roles.some(role => userRoles.includes(role));

    if (!hasRole) {
      return reply.status(403).send({
        error: 'Forbidden',
        message: `Missing required role. Need one of: ${roles.join(', ')}`
      });
    }
  };
}

fastify.get('/api/reports', {
  preHandler: [
    fastify.requireAuth(),
    requireAnyRole('admin', 'manager')
  ]
}, async (request, reply) => {
  return { reports: [] };
});
ロールは Auth0 Actions を使用してトークンに追加する必要があります。トークンにロールを追加する方法について詳しくは、こちらを参照してください
Web アプリケーションからのリクエストを許可するには、CORS を有効にします。
npm install @fastify/cors
server.js
import cors from '@fastify/cors';

await fastify.register(cors, {
  origin: ['http://localhost:3000', 'http://localhost:5173'], // Web アプリの URL
  credentials: true,
});
本番環境では、正確なオリジンを指定します。
server.js
await fastify.register(cors, {
  origin: [
    'https://myapp.com',
    'https://www.myapp.com'
  ],
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
});
認証エラーに対する包括的なエラーハンドリングを追加します。
server.js
// カスタムエラーハンドラー
fastify.setErrorHandler((error, request, reply) => {
  fastify.log.error(error);

  // JWT の検証エラーを処理
  if (error.statusCode === 401) {
    return reply.status(401).send({
      error: 'Unauthorized',
      message: error.message || 'Invalid or missing access token',
      code: 'UNAUTHORIZED'
    });
  }

  // 権限またはロールに関するエラーを処理
  if (error.statusCode === 403) {
    return reply.status(403).send({
      error: 'Forbidden',
      message: error.message || 'Insufficient permissions',
      code: 'FORBIDDEN'
    });
  }

  // その他のエラーを処理
  return reply.status(error.statusCode || 500).send({
    error: 'Internal Server Error',
    message: 'An unexpected error occurred',
    code: 'INTERNAL_ERROR'
  });
});

// Not found ハンドラー
fastify.setNotFoundHandler((request, reply) => {
  return reply.status(404).send({
    error: 'Not Found',
    message: `Route ${request.method} ${request.url} not found`,
    code: 'NOT_FOUND'
  });
});
レート制限を使用して API を悪用から保護します。
npm install @fastify/rate-limit
server.js
import rateLimit from '@fastify/rate-limit';

await fastify.register(rateLimit, {
  max: 100, // 最大リクエスト数
  timeWindow: '1 minute', // 時間ウィンドウ
  errorResponseBuilder: (request, context) => {
    return {
      error: 'Too Many Requests',
      message: `Rate limit exceeded. Try again in ${context.after}`,
      retryAfter: context.after
    };
  }
});

// 特定のルートには、より厳しい制限を適用
fastify.get('/api/expensive-operation', {
  preHandler: fastify.requireAuth(),
  config: {
    rateLimit: {
      max: 10,
      timeWindow: '1 minute'
    }
  }
}, async (request, reply) => {
  return { result: 'expensive operation result' };
});

トラブルシューティング

”No authorization token was found”

問題: API がリクエスト内のアクセストークンを見つけられません。解決策:
  1. Authorization ヘッダーが存在することを確認します: Authorization: Bearer YOUR_TOKEN
  2. トークンの前に Bearer が含まれていることを確認します
  3. トークンの有効期限が切れていないことを確認します

”Invalid token” または “jwt malformed”

問題: トークンの形式が無効です。解決策:
  1. アクセストークン を使用しており、IDトークンではないことを確認します
  2. トークンは API の audience パラメーターを指定して取得する必要があります
  3. トークンが有効な JWT であることを確認します (ドットで区切られた 3 つの部分で構成されている必要があります)

“Invalid signature”

問題: トークンの署名が一致しません。解決策:
  1. AUTH0_DOMAIN がトークンを発行したドメインと一致していることを確認します
  2. RS256 署名アルゴリズム (デフォルト) を使用していることを確認します
  3. トークンが改変されていないことを確認します

”Invalid audience”

問題: トークンの対象者が API と一致しません。解決策: クライアントアプリケーションは、正しい対象者を指定してトークンをリクエストする必要があります。
// クライアントアプリ内
const token = await getAccessTokenSilently({
  authorizationParams: {
    audience: 'https://my-fastify-api.example.com' // API 識別子と一致している必要があります
  }
});

ブラウザーでの CORS エラー

問題: CORS ポリシーにより、ブラウザーが API リクエストをブロックしています。解決策: @fastify/cors をインストールして設定します。
npm install @fastify/cors
import cors from '@fastify/cors';

await fastify.register(cors, {
  origin: 'http://localhost:3000', // フロントエンドの URL
  credentials: true
});

次のステップ

保護された API を用意できたら、次の内容も確認してください。

リソース