Closed19

【失敗】100日チャレンジ day19 (OIDC/OAuthのIDaaS基盤)

riddle_tecriddle_tec

昨日
https://zenn.dev/gin_nazo/scraps/f84a82218807b9


https://blog.framinal.life/entry/2025/04/14/154104

100日チャレンジに感化されたので、アレンジして自分でもやってみます。

やりたいこと

  • 世の中のさまざまなドメインの簡易実装をつくり、バックエンドの実装に慣れる(dbスキーマ設計や、関数の分割、使いやすいインターフェイスの切り方に慣れる
  • 設計力(これはシステムのオーバービューを先に自分で作ってaiに依頼できるようにする
  • 生成aiをつかったバイブコーティングになれる
  • 実際にやったことはzennのスクラップにまとめ、成果はzennのブログにまとめる(アプリ自体の公開は必須ではないかコードはgithubにおく)

できたもの

TBD

riddle_tecriddle_tec

承知しました。Day 19 で作成する OIDC プロバイダー (IDaaS) の基本仕様を以下にまとめます。まずはコア機能に絞り、シンプルさを保ちます。

Day 19: OIDC プロバイダー (IDaaS) - 基本仕様

  1. 目的:

    • 他の独立した Web アプリケーション (クライアントアプリ/RP) から認証・認可のために利用できる、独立した OIDC プロバイダー (IDaaS) を構築する。
    • 異なるクライアントアプリ間でシングルサインオン (SSO) を実現する。
  2. コアプロトコル:

    • OpenID Connect (OIDC) 1.0 に準拠 (OAuth 2.0 を基盤とする)。
    • 主要フローとして Authorization Code Flow をサポートする。
  3. 主要機能:

    • OIDC/OAuth 2.0 エンドポイント:
      • /authorize: 認証リクエストを受け付け、ユーザーにログインと同意を求める。
      • /token: 認可コード (code) を受け取り、IDトークンとアクセストークンを発行する。
      • /userinfo: 有効なアクセストークンに基づき、保護されたユーザー情報 (今回は主にメールアドレス) を返す。
      • /jwks: IDトークンやアクセストークンの署名を検証するための公開鍵セット (JSON Web Key Set) を提供する。
    • ユーザー認証:
      • メールアドレスとパスワードを使用した認証方式を提供。
      • パスワードは安全な方法 (bcryptなど) でハッシュ化してデータベースに保存。
    • クライアントアプリケーション管理:
      • 信頼されたクライアントアプリケーション(例: アプリA, アプリB)の情報をデータベースで管理 (Client ID, Client Secret, Redirect URI など)。
      • クライアント認証は /token エンドポイントで行う (例: Client Secret Basic)。
    • 同意画面:
      • ユーザーがクライアントアプリケーションに対し、どの情報 (スコープ、例: openid, email, profile) へのアクセスを許可するかを選択するインターフェースを提供。
  4. 技術スタック:

    • フレームワーク: Next.js (App Router) - localhost:3001 で動作
    • 言語: TypeScript
    • データベース: SQLite
    • ORM: Prisma
    • OIDCライブラリ: node-oidc-provider (OIDC/OAuth 2.0 の複雑なロジックを管理)
    • スタイリング: Tailwind CSS
    • パスワードハッシュ化: bcrypt
  5. データモデル (Prisma schema.prisma):

    • User: ID、メールアドレス、ハッシュ化パスワードなど。
    • Client: Client ID、ハッシュ化された Client Secret、リダイレクトURIリストなど。
    • その他 node-oidc-provider がアダプター経由で要求する可能性のあるモデル (例: Session, Interaction, Grant) を定義。
  6. 提供する UI (IDaaS 側):

    • ログインページ: ユーザーがメールアドレスとパスワードを入力するフォーム。
    • 同意ページ: ユーザーがクライアントアプリへのスコープ許可を承認/拒否するページ。
    • (任意) ユーザー登録ページ: 今回は必須とせず、開発用に手動でDBにユーザーを作成する形でも可。
  7. テストシナリオ:

    • 別途、簡単なテスト用クライアントアプリ A (localhost:3002) と B (localhost:3003) を作成する。
    • IDaaS (localhost:3001) を起動し、クライアント A, B の情報をDBに登録する。
    • テストユーザーをDBに登録する。
    • ブラウザでアプリ A にアクセス → IDaaS にリダイレクト → ログイン → 同意 → アプリ A に戻りログイン成功。
    • 同じブラウザでアプリ B にアクセス → IDaaS にリダイレクト (ログインはスキップされるはず) → 同意 → アプリ B に戻り同じユーザーでログイン成功 (SSO確認)。
  8. 今回のスコープ外 (実装しない機能):

    • リフレッシュトークン
    • パスワードリセット機能
    • メールアドレス確認フロー
    • 管理者向け管理画面
    • 多要素認証 (MFA)
    • シングルログアウト (SLO)
    • 高度なユーザープロファイル管理 (今回は Email のみ)
riddle_tecriddle_tec

承知しました。OIDC プロバイダー (IDaaS) の実装に向けた作業手順を以下に示します。

Day 19: OIDC プロバイダー (IDaaS) - 作業手順

  1. プロジェクト初期化 & 基本設定:

    • template ディレクトリを day19_oidc_provider としてコピー (再試行)。
    • day19_oidc_provider ディレクトリに移動 (cd day19_oidc_provider)。
    • package.jsonname フィールドを "day19_oidc_provider" に変更。
    • README.md を更新し、今回の「OIDC プロバイダー (IDaaS)」の概要、目的、基本仕様を記述 (既存の内容は置き換え)。
  2. 依存ライブラリの導入:

    • OIDC プロバイダー本体とパスワードハッシュ化ライブラリをインストール:
      npm install oidc-provider bcrypt
      
    • bcrypt の型定義をインストール:
      npm install --save-dev @types/bcrypt
      
  3. データベースモデリング (prisma/schema.prisma):

    • User モデル (id, email, passwordHash) を定義。
    • Client モデル (id, clientId, clientSecretHash, redirectUris, grantTypes) を定義。
    • node-oidc-provider がアダプター経由で必要とするモデル群を定義 (例: Session, AccessToken, AuthorizationCode, RefreshToken, DeviceCode, Interaction, Grant など。詳細は node-oidc-provider ドキュメント参照)。
  4. データベースマイグレーション:

    • スキーマ変更をデータベースに適用:
      npx prisma migrate deploy
      
  5. 環境変数設定 (.env):

    • 必須の環境変数を設定:
      DATABASE_URL="file:./dev.db"
      ISSUER_URL="http://localhost:3001" # IDaaS自身の公開URL
      PORT=3001
      # Cookie署名などに使う秘密鍵 (例: openssl rand -hex 32 で生成)
      SECURE_KEY="your-super-secret-key-change-this"
      # bcrypt のソルトラウンド数
      BCRYPT_SALT_ROUNDS=10
      NEXT_PUBLIC_ISSUER_URL="http://localhost:3001" # クライアントサイドでも使う場合
      
    • .gitignore.env が含まれていることを確認。
  6. OIDC Provider 設定 (lib/oidc.ts):

    • oidc-provider のインスタンスを生成し、基本設定を行う。
      • issuer: ISSUER_URL を設定。
      • clients: DBからクライアント情報をロードする設定 (初期は空配列でも可、後で Prisma Adapter 経由で設定)。
      • cookies.keys: SECURE_KEY を設定。
      • findAccount: 指定された ctxid に基づいてユーザーアカウント情報を返す関数を実装 (Prisma を使用)。
      • claims: サポートするクレーム (openid, email, profile など) を定義。
      • features: 有効にする機能 (例: userinfo, revocation) を設定。resourceIndicators を有効にする場合は設定。
      • pkce.required: true に設定 (Authorization Code Flow with PKCE を強制)。
      • Prisma Adapter の設定: node-oidc-provider が Prisma と連携するためのアダプターを設定/実装する (重要なステップ)。
      • JWKS の設定 (デフォルトでは自動生成される)。
  7. API エンドポイント実装 (app/api/oidc/[...oidc]/route.ts):

    • lib/oidc.ts で作成した provider インスタンスの callback() メソッドを使用して、OIDC のリクエストを処理する Route Handler を作成。
  8. インタラクション UI 実装 (app/(interaction)/[uid]/...):

    • oidc-provider の設定で、ログインと同意のインタラクションが発生した際にリダイレクトするカスタムURL (/interaction/:uid/login, /interaction/:uid/confirm) を設定する。
    • ログインページ (app/(interaction)/[uid]/login/page.tsx):
      • provider.interactionDetails(req, res) を使用してインタラクションの詳細を取得。
      • メールアドレス/パスワード入力フォームを作成。
      • フォーム送信時に Server Action または API Route でユーザー認証 (DB照合、bcrypt.compare) を行い、provider.interactionFinished を呼び出す。
    • 同意ページ (app/(interaction)/[uid]/confirm/page.tsx):
      • provider.interactionDetails(req, res) を使用してクライアント情報や要求スコープを取得・表示。
      • 「許可」「拒否」ボタンを設置。
      • ボタン操作に応じて provider.interactionFinished または provider.interactionResult を呼び出す。
  9. 初期データ登録 (開発用):

    • テスト用のユーザー (User) とクライアントアプリケーション (Client, アプリ A, B の情報) を Prisma を使ってデータベースに登録する。
      • ユーザーパスワードは bcrypt.hash でハッシュ化する。
      • クライアントシークレットもハッシュ化して保存するのが望ましい。
  10. テストクライアント準備 (別プロジェクト):

    • 簡単な Next.js アプリ (または Node.js スクリプト) を 2 つ (client-a, client-b) 作成し、それぞれ異なるポート (3002, 3003) で動作するようにする。
    • openid-client などのライブラリを使用して、IDaaS への認証フローを開始するボタンと、コールバック (/callback) でトークンを取得し、ユーザー情報を表示する機能を実装する。
  11. 結合テスト & デバッグ:

    • IDaaS、クライアントA、クライアントB を起動する。
    • 定義したテストシナリオ(アプリAでログイン→アプリBでSSO確認)を実行し、意図通り動作するか確認。
    • console.log やブラウザの開発者ツール、oidc-provider のデバッグログなどを活用して問題を特定・修正する。
  12. ドキュメント更新:

    • README.md にセットアップ手順、起動方法、テスト方法を追記。
    • .cursor/rules/knowledge.mdc に Day 19 の成果を記録。
riddle_tecriddle_tec

うーん、のっけからなかなか動かなくてこまってるね

riddle_tecriddle_tec

NextAuth.js に使うことを提案された。

oidc-provider v8.x と Node.js v20 (または Next.js/Turbopack 環境) との間に互換性の問題がある可能性が極めて高いです。URL.parse は Node.js の古い API であり、最近のバージョンでは new URL() コンストラクタの使用が推奨されています。ライブラリが古い API に依存していると、このような問題が発生しやすくなります。

riddle_tecriddle_tec

Node.js のバージョンは v20.10.0 ですね。これは oidc-provider が警告している推奨バージョン (v22.x LTS) よりも古いです。
やはり、これが crypto.hash エラーの根本原因である可能性が非常に高いです。Node.js v20 と v22 では crypto モジュール周りの API や内部実装に変更があった可能性があります。
解決策:
Node.js のバージョンを v22.x LTS (現時点での最新 LTS) にアップデートしてください。
Node.js のバージョン管理ツール ( nvm, volta など) をお使いの場合は、それらを使って v22 に切り替えるのが簡単です。

ごめんよ

riddle_tecriddle_tec

アプローチの変更 (推奨):

oidc-provider を別の軽量 Node.js サーバー (例: 標準の http モジュールや fastify など) で実行します。
Next.js の API ルート (/api/oidc/[...oidc]/route.ts) は、その別サーバーへのリクエストプロキシとして機能するように実装します。これは比較的実装が容易で、oidc-provider を本来の環境に近い形で動作させられます。
Next.js の rewrites は、/auth などをこのプロキシ API ルートに転送するように設定します。

riddle_tecriddle_tec

nextjs で oidc サーバをやらせるのは荷が重いことがわかった

riddle_tecriddle_tec

✅ シンプルさと実装しやすさを最優先にした技術選定

役割 採用スタック 採用理由
UI (ログイン/同意画面) Next.js 15 (App Router) + React Server Components + Tailwind CSS React/Tailwind で UI を素早く構築。app/login/page.tsx などファイル=URL で直感的。すべて Node ランタイムなので OIDC と同一プロセスにまとめやすい。
OIDC/OAuth 2.0 コア node-oidc-provider v8 OpenID Certified™ かつ最新の v8 系列がメンテされており、PoC でも十分な機能が一式揃う。
サーバーランタイム Express 5(カスタム Next.js サーバー方式) Next.js をカスタムサーバーモードで起動し、provider.callback() をマウントするだけで一体化できる。
永続化 SQLite + Prisma ORM 依存ファイルは dev.db だけ。Prisma の型安全クライアントで開発が高速化。
OIDC⇔DB アダプター node-oidc-provider-prisma-adapter 既存 OSS があるので自作不要。
パスワードハッシュ bcrypt (bcryptjs でも可) コスト 10 程度なら開発環境でも十分高速。

推奨アーキテクチャ(単一リポジトリ・単一プロセス)

┌────────────┐  http://localhost:3001
│ Next+Express│
│  (custom)   │
│ ┌────────┐ │   /authorize  /token  /userinfo  /jwks
│ │oidc-prov│─┼────────────────────────────────────────┐
│ └────────┘ │                                          │
│   ↑ interaction (302)                                 │
│ ┌────────────┐                                        │
│ │ /login     │  React + Tailwind (Next.js pages)      │
│ │ /consent   │                                        │
│ └────────────┘                                        │
└────────────────────────────────────────────────────────┘

ポイント

  • 画面レンダリングは 全部 Next.js。node-oidc-provider の view 機能は完全にオフ。
  • OIDC ルートだけ /oidc プレフィックスで Express にマウントし、残りは Next.js ルーティングに任せる。

最小構成サンプル

server.ts — Next.js + Express + OIDC Provider

import express from 'express';
import next from 'next';
import { Provider } from 'oidc-provider';
import { PrismaAdapter } from './prisma/adapter';

const dev  = process.env.NODE_ENV !== 'production';
const app  = next({ dev });
const handle = app.getRequestHandler();

(async () => {
  await app.prepare();

  const oidc = new Provider('http://localhost:3001', {
    adapter: PrismaAdapter,
    clients: [
      {
        client_id: 'appA',
        client_secret: 'secretA',
        redirect_uris: ['http://localhost:3002/callback'],
        response_types: ['code'],
        grant_types: ['authorization_code'],
      },
    ],
    features: {
      devInteractions: { enabled: false },
    },
    interactions: {
      url: ctx => `/interaction/${ctx.oidc.uid}`,
    },
  });

  const server = express();
  server.use('/oidc', oidc.callback());  // /oidc/authorize など

  server.all('*', (req, res) => handle(req, res));

  server.listen(3001, () =>
    console.log('🚀  IDaaS running at http://localhost:3001'),
  );
})();

Prisma schema.prisma(抜粋)

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  password  String
  createdAt DateTime @default(now())
}

model Client {
  id            String   @id
  clientSecret  String
  redirectUris  String[]
  grants        String[]
}

/// 以下は adapter が期待するテーブル群
model Session     { @@map("oidc_Session")     /* ... */ }
model Interaction { @@map("oidc_Interaction") /* ... */ }
model Grant       { @@map("oidc_Grant")       /* ... */ }

UI フロー(Next.js 側)

  1. /interaction/[uid]/page.tsx
    provider.interactionDetails() で prompt を取得 → 未ログインなら /login?uid=… へリダイレクト。
  2. /login/page.tsx
    フォーム送信後 provider.interactionFinished(res, uid, { login: { accountId } })
  3. /consent/page.tsx
    スコープ一覧を表示し、承認で interactionFinished

単一プロセス構成を勧める理由

項目 単一プロセス Next / OIDC 分離
セットアップ pnpm i && pnpm dev 2 つの起動スクリプト
ポート管理 3001 だけ 3001 / 4000 など
Cookie ドメイン/CORS 同一オリジンで設定不要 SameSite=None・CORS 設計が必要
デプロイ Dockerfile 1 枚 コンテナ 2 個または Nginx 経由

PoC なら 最速で動く単一構成が圧倒的に楽です。
将来スケール時に OIDC 部分だけを独立させることも容易です。


次のアクション

  1. 上記サンプルをクローンして起動
    • .envDATABASE_URL="file:./dev.db"
    • pnpm devhttp://localhost:3001/oidc/.well-known/openid-configuration が JSON を返せば OK
  2. テスト用クライアント A/B を用意して Authorization Code Flow → SSO 動作を確認
  3. ハマりどころが出たらいつでも質問してください!🙌
riddle_tecriddle_tec

仕切り直し

Day 19: OIDC プロバイダー (IDaaS) - 基本仕様

  1. 目的:

    • 他の独立した Web アプリケーション (クライアントアプリ/RP) から認証・認可のために利用できる、独立した OIDC プロバイダー (IDaaS) を構築する。
    • 異なるクライアントアプリ間でシングルサインオン (SSO) を実現する。
  2. コアプロトコル:

    • OpenID Connect (OIDC) 1.0 に準拠 (OAuth 2.0 を基盤とする)。
    • 主要フローとして Authorization Code Flow をサポートする。
  3. 主要機能:

    • OIDC/OAuth 2.0 エンドポイント:
      • /authorize: 認証リクエストを受け付け、ユーザーにログインと同意を求める。
      • /token: 認可コード (code) を受け取り、IDトークンとアクセストークンを発行する。
      • /userinfo: 有効なアクセストークンに基づき、保護されたユーザー情報 (今回は主にメールアドレス) を返す。
      • /jwks: IDトークンやアクセストークンの署名を検証するための公開鍵セット (JSON Web Key Set) を提供する。
    • ユーザー認証:
      • メールアドレスとパスワードを使用した認証方式を提供。
      • パスワードは安全な方法 (bcryptなど) でハッシュ化してデータベースに保存。
    • クライアントアプリケーション管理:
      • 信頼されたクライアントアプリケーション(例: アプリA, アプリB)の情報をデータベースで管理 (Client ID, Client Secret, Redirect URI など)。
      • クライアント認証は /token エンドポイントで行う (例: Client Secret Basic)。
    • 同意画面:
      • ユーザーがクライアントアプリケーションに対し、どの情報 (スコープ、例: openid, email, profile) へのアクセスを許可するかを選択するインターフェースを提供。
  4. データモデル (Prisma schema.prisma):

    • User: ID、メールアドレス、ハッシュ化パスワードなど。
    • Client: Client ID、ハッシュ化された Client Secret、リダイレクトURIリストなど。
    • その他 node-oidc-provider がアダプター経由で要求する可能性のあるモデル (例: Session, Interaction, Grant) を定義。
  5. 提供する UI (IDaaS 側):

    • ログインページ: ユーザーがメールアドレスとパスワードを入力するフォーム。
    • 同意ページ: ユーザーがクライアントアプリへのスコープ許可を承認/拒否するページ。
    • (任意) ユーザー登録ページ: 今回は必須とせず、開発用に手動でDBにユーザーを作成する形でも可。
  6. テストシナリオ:

    • 別途、簡単なテスト用クライアントアプリ A (localhost:3002) と B (localhost:3003) を作成する。
    • IDaaS (localhost:3001) を起動し、クライアント A, B の情報をDBに登録する。
    • テストユーザーをDBに登録する。
    • ブラウザでアプリ A にアクセス → IDaaS にリダイレクト → ログイン → 同意 → アプリ A に戻りログイン成功。
    • 同じブラウザでアプリ B にアクセス → IDaaS にリダイレクト (ログインはスキップされるはず) → 同意 → アプリ B に戻り同じユーザーでログイン成功 (SSO確認)。
  7. 今回のスコープ外 (実装しない機能):

    • リフレッシュトークン
    • パスワードリセット機能
    • メールアドレス確認フロー
    • 管理者向け管理画面
    • 多要素認証 (MFA)
    • シングルログアウト (SLO)
    • 高度なユーザープロファイル管理 (今回は Email のみ)

✅ シンプルさと実装しやすさを最優先にした技術選定

役割 採用スタック 採用理由
UI (ログイン/同意画面) Next.js 15 (App Router) + React Server Components + Tailwind CSS React/Tailwind で UI を素早く構築。app/login/page.tsx などファイル=URL で直感的。すべて Node ランタイムなので OIDC と同一プロセスにまとめやすい。
OIDC/OAuth 2.0 コア node-oidc-provider v8 OpenID Certified™ かつ最新の v8 系列がメンテされており、PoC でも十分な機能が一式揃う。
サーバーランタイム Express 5(カスタム Next.js サーバー方式) Next.js をカスタムサーバーモードで起動し、provider.callback() をマウントするだけで一体化できる。
永続化 SQLite + Prisma ORM 依存ファイルは dev.db だけ。Prisma の型安全クライアントで開発が高速化。
OIDC⇔DB アダプター node-oidc-provider-prisma-adapter 既存 OSS があるので自作不要。
パスワードハッシュ bcrypt (bcryptjs でも可) コスト 10 程度なら開発環境でも十分高速。

推奨アーキテクチャ(単一リポジトリ・単一プロセス)

┌────────────┐  http://localhost:3001
│ Next+Express│
│  (custom)   │
│ ┌────────┐ │   /authorize  /token  /userinfo  /jwks
│ │oidc-prov│─┼────────────────────────────────────────┐
│ └────────┘ │                                          │
│   ↑ interaction (302)                                 │
│ ┌────────────┐                                        │
│ │ /login     │  React + Tailwind (Next.js pages)      │
│ │ /consent   │                                        │
│ └────────────┘                                        │
└────────────────────────────────────────────────────────┘

ポイント

  • 画面レンダリングは 全部 Next.js。node-oidc-provider の view 機能は完全にオフ。
  • OIDC ルートだけ /oidc プレフィックスで Express にマウントし、残りは Next.js ルーティングに任せる。

最小構成サンプル

server.ts — Next.js + Express + OIDC Provider

import express from 'express';
import next from 'next';
import { Provider } from 'oidc-provider';
import { PrismaAdapter } from './prisma/adapter';

const dev  = process.env.NODE_ENV !== 'production';
const app  = next({ dev });
const handle = app.getRequestHandler();

(async () => {
  await app.prepare();

  const oidc = new Provider('http://localhost:3001', {
    adapter: PrismaAdapter,
    clients: [
      {
        client_id: 'appA',
        client_secret: 'secretA',
        redirect_uris: ['http://localhost:3002/callback'],
        response_types: ['code'],
        grant_types: ['authorization_code'],
      },
    ],
    features: {
      devInteractions: { enabled: false },
    },
    interactions: {
      url: ctx => `/interaction/${ctx.oidc.uid}`,
    },
  });

  const server = express();
  server.use('/oidc', oidc.callback());  // /oidc/authorize など

  server.all('*', (req, res) => handle(req, res));

  server.listen(3001, () =>
    console.log('🚀  IDaaS running at http://localhost:3001'),
  );
})();

Prisma schema.prisma(抜粋)

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  password  String
  createdAt DateTime @default(now())
}

model Client {
  id            String   @id
  clientSecret  String
  redirectUris  String[]
  grants        String[]
}

/// 以下は adapter が期待するテーブル群
model Session     { @@map("oidc_Session")     /* ... */ }
model Interaction { @@map("oidc_Interaction") /* ... */ }
model Grant       { @@map("oidc_Grant")       /* ... */ }

UI フロー(Next.js 側)

  1. /interaction/[uid]/page.tsx
    provider.interactionDetails() で prompt を取得 → 未ログインなら /login?uid=… へリダイレクト。
  2. /login/page.tsx
    フォーム送信後 provider.interactionFinished(res, uid, { login: { accountId } })
  3. /consent/page.tsx
    スコープ一覧を表示し、承認で interactionFinished

単一プロセス構成を勧める理由

項目 単一プロセス Next / OIDC 分離
セットアップ pnpm i && pnpm dev 2 つの起動スクリプト
ポート管理 3001 だけ 3001 / 4000 など
Cookie ドメイン/CORS 同一オリジンで設定不要 SameSite=None・CORS 設計が必要
デプロイ Dockerfile 1 枚 コンテナ 2 個または Nginx 経由

PoC なら 最速で動く単一構成が圧倒的に楽です。
将来スケール時に OIDC 部分だけを独立させることも容易です。


という感じで day19 は進めたいです。
疑問点や質問はありますか?

riddle_tecriddle_tec

承知いたしました。フィードバックありがとうございます。パスワードは平文で保存し、テストクライアントアプリも Day 19 のスコープに含めます。

それでは、修正した作業計画を以下に示します。

作業計画 (修正版)

  1. IDaaS プロジェクト初期化:
    • template をコピーして day19_oidc_provider ディレクトリを作成。
    • package.jsonnameday19_oidc_provider に更新。
    • README.md を更新し、今回の仕様概要を記載。
    • 必要な依存パッケージ (oidc-provider, express, @types/express, node-oidc-provider-prisma-adapter) を追加。
    • カスタムサーバー (server.ts) の基本設定と package.json スクリプト (dev, build, start) をカスタムサーバーを使用するように更新。
  2. IDaaS データモデリングとDB設定:
    • prisma/schema.prismaUser (パスワードは平文 String), Client 及びアダプター要求モデル (Session, Interaction, Grant, DeviceCode, PushedAuthorizationRequest, ReplayDetection, InitialAccessToken, RegistrationAccessToken, AccessToken, AuthorizationCode, RefreshToken) を定義。
    • prisma migrate deploy でDBスキーマ適用。
    • テスト用データ投入:
      • Prisma のシード機能、または簡単なスクリプトを用意し、テスト用ユーザー(例: test@example.com / password)とテストクライアント(アプリA: client_id=appA, client_secret=secretA, redirect_uris=['http://localhost:3002/callback']、アプリB: client_id=appB, client_secret=secretB, redirect_uris=['http://localhost:3003/callback'])をデータベースに登録。
  3. IDaaS OIDC プロバイダー設定:
    • server.tsoidc-provider インスタンスを初期化。
      • PrismaAdapter を使用。
      • clients オプションで DB からクライアント情報を動的に読み込む設定 (または起動時に読み込む)。
      • interactions.url を設定 (/interaction/:uid)。
      • featuresdevInteractions を無効化。
      • jwks の設定 (キー生成・管理)。
      • その他必要な設定 (スコープ openid email profile など)。
    • Express ミドルウェアとして /oidc パスに oidc.callback() をマウント。
  4. IDaaS UI 実装:
    • app/layout.tsx で基本レイアウトと Tailwind CSS の設定。
    • app/interaction/[uid]/page.tsx:
      • provider.interactionDetails() を呼び出し、プロンプトの種類 (login, consent) を判別。
      • ログインが必要なら /login?uid=... へリダイレクト。
      • 同意が必要なら /consent?uid=... へリダイレクト。
    • app/login/page.tsx:
      • メールアドレスとパスワード入力フォーム。
      • 送信時にサーバーアクションまたは API ルートでユーザーをDBから検索・検証 (平文比較)。
      • 成功したら provider.interactionFinished(req, res, { login: { accountId: user.id } }, { mergeWithLastSubmission: false }) を呼び出し、元のインタラクションへリダイレクト。
    • app/consent/page.tsx:
      • provider.interactionDetails() で要求されているスコープ (prompt.details.scopes.new) を表示。
      • 「許可」「拒否」ボタン。
      • 許可時に provider.interactionFinished(req, res, { consent: { grantId: interaction.grantId } }, { mergeWithLastSubmission: true }) を呼び出し、クライアントへリダイレクト。
    • アプリケーションタイトルを "Day 19 - OIDC Provider" に設定。
  5. テストクライアントアプリ (A, B) の準備:
    • template をコピーして day19_oidc_client_aday19_oidc_client_b ディレクトリを作成。
    • それぞれの package.jsonname を更新。
    • それぞれの README.md を更新 (テストクライアントである旨を記載)。
    • 注意: これらのクライアントアプリは非常にシンプルにします。状態管理ライブラリ等は不要で、サーバーサイドで完結する処理を中心に実装します。
  6. テストクライアントアプリ A の実装 (localhost:3002):
    • package.jsondev スクリプトでポートを 3002 に変更。
    • ルートページ (app/page.tsx):
      • 「アプリAでログイン (OIDC)」ボタン。
      • ボタンクリックで IDaaS の /authorize エンドポイントへリダイレクト (Client ID appA, Redirect URI http://localhost:3002/callback, Scope openid email profile, Response Type code)。
    • コールバックページ (app/callback/page.tsx):
      • URLから認可コード (code) を取得。
      • 取得したコードを使って、サーバーサイドから IDaaS の /token エンドポイントにリクエスト (Client ID/Secret を Basic 認証ヘッダーに含める)。
      • IDトークンとアクセストークンを取得。
      • (任意) アクセストークンを使って IDaaS の /userinfo エンドポイントにリクエストし、ユーザー情報を取得。
      • 取得した情報 (例: IDトークンのクレーム、userinfo の結果) を表示。
  7. テストクライアントアプリ B の実装 (localhost:3003):
    • アプリ A とほぼ同様の手順で実装。
    • ポートは 3003、Client ID は appB、Redirect URI は http://localhost:3003/callback を使用。
  8. デバッグとテスト:
    • 3つのターミナルを開く。
    • ターミナル1: cd day19_oidc_provider && npm run dev (IDaaS を :3001 で起動)。
    • ターミナル2: cd day19_oidc_client_a && npm run dev (アプリ A を :3002 で起動)。
    • ターミナル3: cd day19_oidc_client_b && npm run dev (アプリ B を :3003 で起動)。
    • ブラウザで http://localhost:3002 にアクセス。
    • ログインボタンをクリック → IDaaS にリダイレクトされることを確認。
    • テストユーザー情報 (test@example.com / password) でログイン → 同意画面が表示されることを確認。
    • 同意 → アプリ A (localhost:3002/callback) にリダイレクトされ、ユーザー情報が表示されることを確認。
    • 同じブラウザで別タブを開き http://localhost:3003 にアクセス。
    • ログインボタンをクリック → IDaaS にリダイレクトされることを確認 (ログイン画面はスキップされるはず)。
    • 同意画面が表示されることを確認。
    • 同意 → アプリ B (localhost:3003/callback) にリダイレクトされ、同じユーザー情報が表示されることを確認 (SSO成功)。
    • curl http://localhost:3001/oidc/.well-known/openid-configuration で設定確認。
    • curl http://localhost:3001/oidc/jwks で公開鍵確認。
    • (アプリA/Bで取得したアクセストークンを使い) curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" http://localhost:3001/oidc/me (/userinfo相当) でユーザー情報取得確認。
    • 不要なファイルやコードを削除。
  9. ドキュメント作成:
    • day19_oidc_provider/README.md にセットアップ方法、テスト手順を記載。
    • day19_oidc_client_a/README.md, day19_oidc_client_b/README.md に簡単な説明を記載。
    • .cursor/rules/knowledge.mdc に Day 19 の実装内容を追記。

この計画で進めてよろしいでしょうか? よろしければ、まず IDaaS のプロジェクト初期化から開始します。

riddle_tecriddle_tec

さらに仕切り直し


承知いたしました。Next.js + Node.js での問題発生を踏まえ、バックエンドを Go で実装し、IDaaS 側のフロントエンド(ログイン・同意画面)を Next.js で実装する方針で進めます。

以下、Go バックエンド + Next.js フロントエンドでの作業計画です。

作業計画:

  1. プロジェクト初期化:

    • template ディレクトリを day19_oidc_provider にコピーします。
    • package.jsonname フィールドを day19-oidc-provider に変更します。
    • README.md に今回の仕様概要 (OIDC プロバイダー、Go バックエンド + Next.js フロントエンド) を記述します。
  2. Go バックエンド セットアップ:

    • day19_oidc_provider 内に backend_go ディレクトリを作成します。
    • backend_gogo mod init github.com/user/day19_oidc_provider/backend_go (ユーザー名は適宜変更) を実行します。
    • go get で必要なライブラリを追加します:
      • github.com/go-chi/chi/v5 (ルーター)
      • github.com/go-chi/cors (CORS ミドルウェア)
      • golang.org/x/oauth2 (OAuth2 クライアント、一部ヘルパー利用)
      • github.com/coreos/go-oidc/v3/oidc (OIDC クライアント、検証用)
      • github.com/golang-jwt/jwt/v5 (JWT 生成・検証)
      • golang.org/x/crypto/bcrypt (パスワードハッシュ化)
      • github.com/mattn/go-sqlite3 (SQLite ドライバー)
      • github.com/jmoiron/sqlx (DB 操作ヘルパー)
      • github.com/google/uuid (UUID 生成)
      • (マイグレーションツール golang-migrate/migrate の CLI をインストール)
  3. データモデリングと DB 設定 (Go):

    • backend_go/db/migrations ディレクトリを作成し、golang-migrate/migrate 用のマイグレーションファイル (SQL) を作成します (User, Client, AuthorizationCode, Session テーブルなど)。
    • backend_go/internal/store パッケージなどで DB 操作ロジックを実装します (sqlx を使用)。
    • マイグレーションを実行して prisma/dev.db (今回はこれを共有) にテーブルを作成します。
    • (任意) テストユーザーとクライアント情報を登録するスクリプトまたはロジックを作成します。
  4. Go バックエンド OIDC コア実装:

    • 設定: Issuer URL, JWKS URI, 秘密鍵などを設定ファイルまたは環境変数で管理します。
    • JWKS: RSA キーペアを生成・保持し、/jwks エンドポイントを実装します。
    • ハンドラー: chi ルーターを使用して各エンドポイント (/authorize, /token, /userinfo, /jwks, /login, /consent) のハンドラー関数を作成します。
    • 認証/同意フロー:
      • /authorize で認証/同意が必要な場合、一時的な ID (例: interaction_id) を生成し、対応する Next.js ページ (例: http://localhost:3001/login?interaction_id=...) にリダイレクトします。
      • Next.js 側 (/login, /consent) のフォーム送信先として /login (POST), /consent (POST) を実装し、interaction_id を元に状態を復元して処理を進めます。
    • トークン発行: ID トークン (JWT)、アクセストークン (JWT or Opaque) を生成します。
    • セッション管理: ログイン状態を管理するセッション機構 (例: Cookie ストア) を実装します。
    • DB連携: internal/store の関数を利用してデータを永続化・取得します。
  5. Next.js フロントエンド実装 (UI):

    • app/(interaction)/login/page.tsx: ログインフォーム。Go バックエンドの /login (POST) に interaction_id と共に送信します。
    • app/(interaction)/consent/page.tsx: 同意フォーム。Go バックエンドの /consent (POST) に interaction_id と共に送信します。
    • Go バックエンドからリダイレクトされた際に interaction_id をクエリパラメータから取得し、フォームに含める処理を追加します。
    • Tailwind CSS でスタイリングします。
  6. 連携とテスト:

    • Go バックエンド (例: ポート 8080) と Next.js フロントエンド (ポート 3001) を起動します。
    • テスト用クライアント A (ポート 3002), B (ポート 3003) を別途作成し、Go バックエンド (例: http://localhost:8080) を IDaaS として設定します。
    • ブラウザでテストシナリオを実行し、ログイン、同意、SSO を確認します。
    • curl で Go バックエンドの OIDC エンドポイントをテストします。
  7. ドキュメント作成:

    • README.md.cursor/rules/knowledge.mdc を更新します。

それでは、作業を開始します。

1. プロジェクト初期化
まず、template ディレクトリをコピーして day19_oidc_provider ディレクトリを作成し、package.jsonREADME.md を更新します。

riddle_tecriddle_tec

これ今更だけどサーバとフロントと oidc provider で別に要件つくって、IF定義作ってもらってそれぞれ別個に実装してもらった方が絶対良かったな。

1コンテキストで全部やらせようとしたせいでめちゃくちゃ大変なことになってる

riddle_tecriddle_tec

ちょっと一旦 OIDC はやり直そう。。いつか再挑戦する。

riddle_tecriddle_tec

はい、率直に言って、ご指摘の通りだと思います。このプロジェクトは以下の理由から分割して実装する方が良かったでしょう:

  1. 責任範囲の明確化

    • OIDCプロバイダー
    • フロントエンド
    • クライアントアプリケーション
      これらが明確に分離されていれば、各コンポーネントの役割と責任が明確になります。
  2. インターフェース設計の重要性

    • 先にAPI仕様書(OpenAPIなど)を作成し
    • 明確なインターフェース契約を決めてから
    • 各コンポーネントを独立して実装する方が混乱が少なくなります
  3. デバッグの容易さ

    • 今回のように「どこで問題が発生しているか」を特定するのが難しい状況を避けられます
    • 各部分が独立していれば、問題の切り分けが容易になります
  4. 複雑性の管理

    • OIDCプロバイダーのような複雑なシステムは、それだけで十分に複雑です
    • フロントエンドとの統合を同時に行うと、複雑性が掛け算的に増加します
  5. 学習効果の向上

    • まずはOIDCの仕様と実装に集中
    • 次にフロントエンド連携
      という段階的アプローチの方が学習効果も高いでしょう

リベンジする際には、まずはOIDCプロバイダーのバックエンドを単体で実装し、APIテストツールで動作確認してから、フロントエンドとの連携に進むことをお勧めします。また、クライアントアプリケーションも別途実装することで、全体としてのOIDCフローを理解しやすくなります。

このスクラップは3ヶ月前にクローズされました